about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-04-18 13:49:17 +0000
committerbors <bors@rust-lang.org>2025-04-18 13:49:17 +0000
commit191df20fcad9331d3a948aa8e8556775ec3fe69d (patch)
treef74fb63b5aa5487852469be80310c79a9ff95f2f /src
parent6a0bd27619a997ba7f040a28a41c3511423a3117 (diff)
parent8cb57ed74bcd58b0499e5f9dd5e39cbef804d83f (diff)
downloadrust-191df20fcad9331d3a948aa8e8556775ec3fe69d.tar.gz
rust-191df20fcad9331d3a948aa8e8556775ec3fe69d.zip
Auto merge of #139996 - matthiaskrgr:rollup-0nka2hw, r=matthiaskrgr
Rollup of 7 pull requests

Successful merges:

 - #138528 (deref patterns: implement implicit deref patterns)
 - #139393 (rustdoc-json: Output target feature information)
 - #139553 (sync::mpsc: prevent double free on `Drop`)
 - #139615 (Remove `name_or_empty`)
 - #139853 (Disable combining LLD with external llvm-config)
 - #139913 (rustdoc/clean: Fix lowering of fn params (fixes correctness & HIR vs. middle parity regressions))
 - #139942 (Ignore aix for tests/ui/erros/pic-linker.rs)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/src/core/build_steps/compile.rs12
-rw-r--r--src/bootstrap/src/core/build_steps/dist.rs9
-rw-r--r--src/bootstrap/src/core/build_steps/llvm.rs6
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs4
-rw-r--r--src/bootstrap/src/core/builder/tests.rs4
-rw-r--r--src/bootstrap/src/core/config/config.rs42
-rw-r--r--src/bootstrap/src/core/sanity.rs2
-rw-r--r--src/bootstrap/src/lib.rs42
-rw-r--r--src/doc/unstable-book/src/language-features/box-patterns.md4
-rw-r--r--src/doc/unstable-book/src/language-features/deref-patterns.md57
-rw-r--r--src/doc/unstable-book/src/language-features/string-deref-patterns.md3
-rw-r--r--src/librustdoc/clean/mod.rs188
-rw-r--r--src/librustdoc/clean/types.rs18
-rw-r--r--src/librustdoc/clean/utils.rs9
-rw-r--r--src/librustdoc/core.rs4
-rw-r--r--src/librustdoc/doctest/make.rs14
-rw-r--r--src/librustdoc/html/format.rs54
-rw-r--r--src/librustdoc/html/render/context.rs12
-rw-r--r--src/librustdoc/html/render/search_index.rs12
-rw-r--r--src/librustdoc/json/conversions.rs9
-rw-r--r--src/librustdoc/json/mod.rs60
-rw-r--r--src/rustdoc-json-types/lib.rs58
-rw-r--r--src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs4
-rw-r--r--src/tools/compiletest/src/directive-list.rs3
-rw-r--r--src/tools/jsondocck/src/main.rs2
-rw-r--r--src/tools/jsondoclint/src/validator/tests.rs4
-rw-r--r--src/tools/miri/tests/pass/issues/issue-139553.rs45
28 files changed, 444 insertions, 239 deletions
diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index dab58fccf5e..6a5b38dd504 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -155,7 +155,7 @@ impl Step for Std {
 
         // When using `download-rustc`, we already have artifacts for the host available. Don't
         // recompile them.
-        if builder.download_rustc() && builder.is_builder_target(target)
+        if builder.download_rustc() && builder.config.is_host_target(target)
             // NOTE: the beta compiler may generate different artifacts than the downloaded compiler, so
             // its artifacts can't be reused.
             && compiler.stage != 0
@@ -229,7 +229,7 @@ impl Step for Std {
         // The LLD wrappers and `rust-lld` are self-contained linking components that can be
         // necessary to link the stdlib on some targets. We'll also need to copy these binaries to
         // the `stage0-sysroot` to ensure the linker is found when bootstrapping on such a target.
-        if compiler.stage == 0 && builder.is_builder_target(compiler.host) {
+        if compiler.stage == 0 && builder.config.is_host_target(compiler.host) {
             trace!(
                 "(build == host) copying linking components to `stage0-sysroot` for bootstrapping"
             );
@@ -1374,7 +1374,7 @@ pub fn rustc_cargo_env(
 /// Pass down configuration from the LLVM build into the build of
 /// rustc_llvm and rustc_codegen_llvm.
 fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelection) {
-    if builder.is_rust_llvm(target) {
+    if builder.config.is_rust_llvm(target) {
         cargo.env("LLVM_RUSTLLVM", "1");
     }
     if builder.config.llvm_enzyme {
@@ -2182,7 +2182,7 @@ impl Step for Assemble {
         debug!("copying codegen backends to sysroot");
         copy_codegen_backends_to_sysroot(builder, build_compiler, target_compiler);
 
-        if builder.config.lld_enabled {
+        if builder.config.lld_enabled && !builder.config.is_system_llvm(target_compiler.host) {
             builder.ensure(crate::core::build_steps::tool::LldWrapper {
                 build_compiler,
                 target_compiler,
@@ -2532,7 +2532,9 @@ pub fn strip_debug(builder: &Builder<'_>, target: TargetSelection, path: &Path)
     // FIXME: to make things simpler for now, limit this to the host and target where we know
     // `strip -g` is both available and will fix the issue, i.e. on a x64 linux host that is not
     // cross-compiling. Expand this to other appropriate targets in the future.
-    if target != "x86_64-unknown-linux-gnu" || !builder.is_builder_target(target) || !path.exists()
+    if target != "x86_64-unknown-linux-gnu"
+        || !builder.config.is_host_target(target)
+        || !path.exists()
     {
         return;
     }
diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs
index 83f71aeed72..ed90ede7936 100644
--- a/src/bootstrap/src/core/build_steps/dist.rs
+++ b/src/bootstrap/src/core/build_steps/dist.rs
@@ -612,7 +612,7 @@ impl Step for DebuggerScripts {
 fn skip_host_target_lib(builder: &Builder<'_>, compiler: Compiler) -> bool {
     // The only true set of target libraries came from the build triple, so
     // let's reduce redundant work by only producing archives from that host.
-    if !builder.is_builder_target(compiler.host) {
+    if !builder.config.is_host_target(compiler.host) {
         builder.info("\tskipping, not a build host");
         true
     } else {
@@ -671,7 +671,8 @@ fn copy_target_libs(
                 &self_contained_dst.join(path.file_name().unwrap()),
                 FileType::NativeLibrary,
             );
-        } else if dependency_type == DependencyType::Target || builder.is_builder_target(target) {
+        } else if dependency_type == DependencyType::Target || builder.config.is_host_target(target)
+        {
             builder.copy_link(&path, &dst.join(path.file_name().unwrap()), FileType::NativeLibrary);
         }
     }
@@ -824,7 +825,7 @@ impl Step for Analysis {
     fn run(self, builder: &Builder<'_>) -> Option<GeneratedTarball> {
         let compiler = self.compiler;
         let target = self.target;
-        if !builder.is_builder_target(compiler.host) {
+        if !builder.config.is_host_target(compiler.host) {
             return None;
         }
 
@@ -2118,7 +2119,7 @@ fn maybe_install_llvm(
     //
     // If the LLVM is coming from ourselves (just from CI) though, we
     // still want to install it, as it otherwise won't be available.
-    if builder.is_system_llvm(target) {
+    if builder.config.is_system_llvm(target) {
         trace!("system LLVM requested, no install");
         return false;
     }
diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs
index 6e84b83d17d..6f6839ad15b 100644
--- a/src/bootstrap/src/core/build_steps/llvm.rs
+++ b/src/bootstrap/src/core/build_steps/llvm.rs
@@ -485,7 +485,7 @@ impl Step for Llvm {
         }
 
         // https://llvm.org/docs/HowToCrossCompileLLVM.html
-        if !builder.is_builder_target(target) {
+        if !builder.config.is_host_target(target) {
             let LlvmResult { llvm_config, .. } =
                 builder.ensure(Llvm { target: builder.config.build });
             if !builder.config.dry_run() {
@@ -637,7 +637,7 @@ fn configure_cmake(
     }
     cfg.target(&target.triple).host(&builder.config.build.triple);
 
-    if !builder.is_builder_target(target) {
+    if !builder.config.is_host_target(target) {
         cfg.define("CMAKE_CROSSCOMPILING", "True");
 
         // NOTE: Ideally, we wouldn't have to do this, and `cmake-rs` would just handle it for us.
@@ -1098,7 +1098,7 @@ impl Step for Lld {
             .define("LLVM_CMAKE_DIR", llvm_cmake_dir)
             .define("LLVM_INCLUDE_TESTS", "OFF");
 
-        if !builder.is_builder_target(target) {
+        if !builder.config.is_host_target(target) {
             // Use the host llvm-tblgen binary.
             cfg.define(
                 "LLVM_TABLEGEN_EXE",
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index b1a3bba0887..096f7de6597 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -1894,7 +1894,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
                     .arg(llvm_components.trim());
                 llvm_components_passed = true;
             }
-            if !builder.is_rust_llvm(target) {
+            if !builder.config.is_rust_llvm(target) {
                 cmd.arg("--system-llvm");
             }
 
@@ -2668,7 +2668,7 @@ impl Step for Crate {
             cargo
         } else {
             // Also prepare a sysroot for the target.
-            if !builder.is_builder_target(target) {
+            if !builder.config.is_host_target(target) {
                 builder.ensure(compile::Std::new(compiler, target).force_recompile(true));
                 builder.ensure(RemoteCopyLibs { compiler, target });
             }
diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs
index fd3b28e4e6a..5de824ebab2 100644
--- a/src/bootstrap/src/core/builder/tests.rs
+++ b/src/bootstrap/src/core/builder/tests.rs
@@ -1107,8 +1107,8 @@ fn test_is_builder_target() {
         let build = Build::new(config);
         let builder = Builder::new(&build);
 
-        assert!(builder.is_builder_target(target1));
-        assert!(!builder.is_builder_target(target2));
+        assert!(builder.config.is_host_target(target1));
+        assert!(!builder.config.is_host_target(target2));
     }
 }
 
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index 2266e61bf60..cd9706646ac 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -2397,6 +2397,12 @@ impl Config {
             );
         }
 
+        if config.lld_enabled && config.is_system_llvm(config.build) {
+            eprintln!(
+                "Warning: LLD is enabled when using external llvm-config. LLD will not be built and copied to the sysroot."
+            );
+        }
+
         let default_std_features = BTreeSet::from([String::from("panic-unwind")]);
         config.rust_std_features = std_features.unwrap_or(default_std_features);
 
@@ -3240,6 +3246,42 @@ impl Config {
 
         Some(commit.to_string())
     }
+
+    /// Checks if the given target is the same as the host target.
+    pub fn is_host_target(&self, target: TargetSelection) -> bool {
+        self.build == target
+    }
+
+    /// Returns `true` if this is an external version of LLVM not managed by bootstrap.
+    /// In particular, we expect llvm sources to be available when this is false.
+    ///
+    /// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set.
+    pub fn is_system_llvm(&self, target: TargetSelection) -> bool {
+        match self.target_config.get(&target) {
+            Some(Target { llvm_config: Some(_), .. }) => {
+                let ci_llvm = self.llvm_from_ci && self.is_host_target(target);
+                !ci_llvm
+            }
+            // We're building from the in-tree src/llvm-project sources.
+            Some(Target { llvm_config: None, .. }) => false,
+            None => false,
+        }
+    }
+
+    /// Returns `true` if this is our custom, patched, version of LLVM.
+    ///
+    /// This does not necessarily imply that we're managing the `llvm-project` submodule.
+    pub fn is_rust_llvm(&self, target: TargetSelection) -> bool {
+        match self.target_config.get(&target) {
+            // We're using a user-controlled version of LLVM. The user has explicitly told us whether the version has our patches.
+            // (They might be wrong, but that's not a supported use-case.)
+            // In particular, this tries to support `submodules = false` and `patches = false`, for using a newer version of LLVM that's not through `rust-lang/llvm-project`.
+            Some(Target { llvm_has_rust_patches: Some(patched), .. }) => *patched,
+            // The user hasn't promised the patches match.
+            // This only has our patches if it's downloaded from CI or built from source.
+            _ => !self.is_system_llvm(target),
+        }
+    }
 }
 
 /// Compares the current `Llvm` options against those in the CI LLVM builder and detects any incompatible options.
diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs
index 9e4a72bc9c3..eb0bf1d166a 100644
--- a/src/bootstrap/src/core/sanity.rs
+++ b/src/bootstrap/src/core/sanity.rs
@@ -326,7 +326,7 @@ than building it.
         if target.contains("musl") && !target.contains("unikraft") {
             // If this is a native target (host is also musl) and no musl-root is given,
             // fall back to the system toolchain in /usr before giving up
-            if build.musl_root(*target).is_none() && build.is_builder_target(*target) {
+            if build.musl_root(*target).is_none() && build.config.is_host_target(*target) {
                 let target = build.config.target_config.entry(*target).or_default();
                 target.musl_root = Some("/usr".into());
             }
diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index 1a513a240e1..88d181532a7 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -35,7 +35,7 @@ use utils::channel::GitInfo;
 
 use crate::core::builder;
 use crate::core::builder::Kind;
-use crate::core::config::{DryRun, LldMode, LlvmLibunwind, Target, TargetSelection, flags};
+use crate::core::config::{DryRun, LldMode, LlvmLibunwind, TargetSelection, flags};
 use crate::utils::exec::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode, command};
 use crate::utils::helpers::{
     self, dir_is_empty, exe, libdir, output, set_file_times, split_debuginfo, symlink_dir,
@@ -803,7 +803,7 @@ impl Build {
     /// Note that if LLVM is configured externally then the directory returned
     /// will likely be empty.
     fn llvm_out(&self, target: TargetSelection) -> PathBuf {
-        if self.config.llvm_from_ci && self.is_builder_target(target) {
+        if self.config.llvm_from_ci && self.config.is_host_target(target) {
             self.config.ci_llvm_root()
         } else {
             self.out.join(target).join("llvm")
@@ -851,37 +851,6 @@ impl Build {
         if self.config.vendor { Some(self.src.join(VENDOR_DIR)) } else { None }
     }
 
-    /// Returns `true` if this is an external version of LLVM not managed by bootstrap.
-    /// In particular, we expect llvm sources to be available when this is false.
-    ///
-    /// NOTE: this is not the same as `!is_rust_llvm` when `llvm_has_patches` is set.
-    fn is_system_llvm(&self, target: TargetSelection) -> bool {
-        match self.config.target_config.get(&target) {
-            Some(Target { llvm_config: Some(_), .. }) => {
-                let ci_llvm = self.config.llvm_from_ci && self.is_builder_target(target);
-                !ci_llvm
-            }
-            // We're building from the in-tree src/llvm-project sources.
-            Some(Target { llvm_config: None, .. }) => false,
-            None => false,
-        }
-    }
-
-    /// Returns `true` if this is our custom, patched, version of LLVM.
-    ///
-    /// This does not necessarily imply that we're managing the `llvm-project` submodule.
-    fn is_rust_llvm(&self, target: TargetSelection) -> bool {
-        match self.config.target_config.get(&target) {
-            // We're using a user-controlled version of LLVM. The user has explicitly told us whether the version has our patches.
-            // (They might be wrong, but that's not a supported use-case.)
-            // In particular, this tries to support `submodules = false` and `patches = false`, for using a newer version of LLVM that's not through `rust-lang/llvm-project`.
-            Some(Target { llvm_has_rust_patches: Some(patched), .. }) => *patched,
-            // The user hasn't promised the patches match.
-            // This only has our patches if it's downloaded from CI or built from source.
-            _ => !self.is_system_llvm(target),
-        }
-    }
-
     /// Returns the path to `FileCheck` binary for the specified target
     fn llvm_filecheck(&self, target: TargetSelection) -> PathBuf {
         let target_config = self.config.target_config.get(&target);
@@ -1356,7 +1325,7 @@ Executed at: {executed_at}"#,
             // need to use CXX compiler as linker to resolve the exception functions
             // that are only existed in CXX libraries
             Some(self.cxx.borrow()[&target].path().into())
-        } else if !self.is_builder_target(target)
+        } else if !self.config.is_host_target(target)
             && helpers::use_host_linker(target)
             && !target.is_msvc()
         {
@@ -2025,11 +1994,6 @@ to download LLVM rather than building it.
         stream.reset().unwrap();
         result
     }
-
-    /// Checks if the given target is the same as the builder target.
-    fn is_builder_target(&self, target: TargetSelection) -> bool {
-        self.config.build == target
-    }
 }
 
 #[cfg(unix)]
diff --git a/src/doc/unstable-book/src/language-features/box-patterns.md b/src/doc/unstable-book/src/language-features/box-patterns.md
index a1ac09633b7..c8a15b8477e 100644
--- a/src/doc/unstable-book/src/language-features/box-patterns.md
+++ b/src/doc/unstable-book/src/language-features/box-patterns.md
@@ -6,6 +6,8 @@ The tracking issue for this feature is: [#29641]
 
 ------------------------
 
+> **Note**: This feature will be superseded by [`deref_patterns`] in the future.
+
 Box patterns let you match on `Box<T>`s:
 
 
@@ -28,3 +30,5 @@ fn main() {
     }
 }
 ```
+
+[`deref_patterns`]: ./deref-patterns.md
diff --git a/src/doc/unstable-book/src/language-features/deref-patterns.md b/src/doc/unstable-book/src/language-features/deref-patterns.md
new file mode 100644
index 00000000000..d0102a665b0
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/deref-patterns.md
@@ -0,0 +1,57 @@
+# `deref_patterns`
+
+The tracking issue for this feature is: [#87121]
+
+[#87121]: https://github.com/rust-lang/rust/issues/87121
+
+------------------------
+
+> **Note**: This feature is incomplete. In the future, it is meant to supersede
+> [`box_patterns`](./box-patterns.md) and [`string_deref_patterns`](./string-deref-patterns.md).
+
+This feature permits pattern matching on [smart pointers in the standard library] through their
+`Deref` target types, either implicitly or with explicit `deref!(_)` patterns (the syntax of which
+is currently a placeholder).
+
+```rust
+#![feature(deref_patterns)]
+#![allow(incomplete_features)]
+
+let mut v = vec![Box::new(Some(0))];
+
+// Implicit dereferences are inserted when a pattern can match against the
+// result of repeatedly dereferencing but can't match against a smart
+// pointer itself. This works alongside match ergonomics for references.
+if let [Some(x)] = &mut v {
+    *x += 1;
+}
+
+// Explicit `deref!(_)` patterns may instead be used when finer control is
+// needed, e.g. to dereference only a single smart pointer, or to bind the
+// the result of dereferencing to a variable.
+if let deref!([deref!(opt_x @ Some(1))]) = &mut v {
+    opt_x.as_mut().map(|x| *x += 1);
+}
+
+assert_eq!(v, [Box::new(Some(2))]);
+```
+
+Without this feature, it may be necessary to introduce temporaries to represent dereferenced places
+when matching on nested structures:
+
+```rust
+let mut v = vec![Box::new(Some(0))];
+if let [b] = &mut *v {
+    if let Some(x) = &mut **b {
+        *x += 1;
+    }
+}
+if let [b] = &mut *v {
+    if let opt_x @ Some(1) = &mut **b {
+        opt_x.as_mut().map(|x| *x += 1);
+    }
+}
+assert_eq!(v, [Box::new(Some(2))]);
+```
+
+[smart pointers in the standard library]: https://doc.rust-lang.org/std/ops/trait.DerefPure.html#implementors
diff --git a/src/doc/unstable-book/src/language-features/string-deref-patterns.md b/src/doc/unstable-book/src/language-features/string-deref-patterns.md
index 3723830751e..366bb15d4ea 100644
--- a/src/doc/unstable-book/src/language-features/string-deref-patterns.md
+++ b/src/doc/unstable-book/src/language-features/string-deref-patterns.md
@@ -6,6 +6,8 @@ The tracking issue for this feature is: [#87121]
 
 ------------------------
 
+> **Note**: This feature will be superseded by [`deref_patterns`] in the future.
+
 This feature permits pattern matching `String` to `&str` through [its `Deref` implementation].
 
 ```rust
@@ -42,4 +44,5 @@ pub fn is_it_the_answer(value: Value) -> bool {
 }
 ```
 
+[`deref_patterns`]: ./deref-patterns.md
 [its `Deref` implementation]: https://doc.rust-lang.org/std/string/struct.String.html#impl-Deref-for-String
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index fe9dc9a9e21..034ecb2f6c1 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -1052,7 +1052,7 @@ fn clean_fn_or_proc_macro<'tcx>(
     match macro_kind {
         Some(kind) => clean_proc_macro(item, name, kind, cx),
         None => {
-            let mut func = clean_function(cx, sig, generics, FunctionArgs::Body(body_id));
+            let mut func = clean_function(cx, sig, generics, ParamsSrc::Body(body_id));
             clean_fn_decl_legacy_const_generics(&mut func, attrs);
             FunctionItem(func)
         }
@@ -1071,16 +1071,11 @@ fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[hir::Attrib
         for (pos, literal) in meta_item_list.iter().filter_map(|meta| meta.lit()).enumerate() {
             match literal.kind {
                 ast::LitKind::Int(a, _) => {
-                    let param = func.generics.params.remove(0);
-                    if let GenericParamDef {
-                        name,
-                        kind: GenericParamDefKind::Const { ty, .. },
-                        ..
-                    } = param
-                    {
-                        func.decl.inputs.values.insert(
+                    let GenericParamDef { name, kind, .. } = func.generics.params.remove(0);
+                    if let GenericParamDefKind::Const { ty, .. } = kind {
+                        func.decl.inputs.insert(
                             a.get() as _,
-                            Argument { name: Some(name), type_: *ty, is_const: true },
+                            Parameter { name: Some(name), type_: *ty, is_const: true },
                         );
                     } else {
                         panic!("unexpected non const in position {pos}");
@@ -1092,7 +1087,7 @@ fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[hir::Attrib
     }
 }
 
-enum FunctionArgs<'tcx> {
+enum ParamsSrc<'tcx> {
     Body(hir::BodyId),
     Idents(&'tcx [Option<Ident>]),
 }
@@ -1101,86 +1096,62 @@ fn clean_function<'tcx>(
     cx: &mut DocContext<'tcx>,
     sig: &hir::FnSig<'tcx>,
     generics: &hir::Generics<'tcx>,
-    args: FunctionArgs<'tcx>,
+    params: ParamsSrc<'tcx>,
 ) -> Box<Function> {
     let (generics, decl) = enter_impl_trait(cx, |cx| {
-        // NOTE: generics must be cleaned before args
+        // NOTE: Generics must be cleaned before params.
         let generics = clean_generics(generics, cx);
-        let args = match args {
-            FunctionArgs::Body(body_id) => {
-                clean_args_from_types_and_body_id(cx, sig.decl.inputs, body_id)
-            }
-            FunctionArgs::Idents(idents) => {
-                clean_args_from_types_and_names(cx, sig.decl.inputs, idents)
-            }
+        let params = match params {
+            ParamsSrc::Body(body_id) => clean_params_via_body(cx, sig.decl.inputs, body_id),
+            // Let's not perpetuate anon params from Rust 2015; use `_` for them.
+            ParamsSrc::Idents(idents) => clean_params(cx, sig.decl.inputs, idents, |ident| {
+                Some(ident.map_or(kw::Underscore, |ident| ident.name))
+            }),
         };
-        let decl = clean_fn_decl_with_args(cx, sig.decl, Some(&sig.header), args);
+        let decl = clean_fn_decl_with_params(cx, sig.decl, Some(&sig.header), params);
         (generics, decl)
     });
     Box::new(Function { decl, generics })
 }
 
-fn clean_args_from_types_and_names<'tcx>(
+fn clean_params<'tcx>(
     cx: &mut DocContext<'tcx>,
     types: &[hir::Ty<'tcx>],
     idents: &[Option<Ident>],
-) -> Arguments {
-    fn nonempty_name(ident: &Option<Ident>) -> Option<Symbol> {
-        if let Some(ident) = ident
-            && ident.name != kw::Underscore
-        {
-            Some(ident.name)
-        } else {
-            None
-        }
-    }
-
-    // If at least one argument has a name, use `_` as the name of unnamed
-    // arguments. Otherwise omit argument names.
-    let default_name = if idents.iter().any(|ident| nonempty_name(ident).is_some()) {
-        Some(kw::Underscore)
-    } else {
-        None
-    };
-
-    Arguments {
-        values: types
-            .iter()
-            .enumerate()
-            .map(|(i, ty)| Argument {
-                type_: clean_ty(ty, cx),
-                name: idents.get(i).and_then(nonempty_name).or(default_name),
-                is_const: false,
-            })
-            .collect(),
-    }
+    postprocess: impl Fn(Option<Ident>) -> Option<Symbol>,
+) -> Vec<Parameter> {
+    types
+        .iter()
+        .enumerate()
+        .map(|(i, ty)| Parameter {
+            name: postprocess(idents[i]),
+            type_: clean_ty(ty, cx),
+            is_const: false,
+        })
+        .collect()
 }
 
-fn clean_args_from_types_and_body_id<'tcx>(
+fn clean_params_via_body<'tcx>(
     cx: &mut DocContext<'tcx>,
     types: &[hir::Ty<'tcx>],
     body_id: hir::BodyId,
-) -> Arguments {
-    let body = cx.tcx.hir_body(body_id);
-
-    Arguments {
-        values: types
-            .iter()
-            .zip(body.params)
-            .map(|(ty, param)| Argument {
-                name: Some(name_from_pat(param.pat)),
-                type_: clean_ty(ty, cx),
-                is_const: false,
-            })
-            .collect(),
-    }
+) -> Vec<Parameter> {
+    types
+        .iter()
+        .zip(cx.tcx.hir_body(body_id).params)
+        .map(|(ty, param)| Parameter {
+            name: Some(name_from_pat(param.pat)),
+            type_: clean_ty(ty, cx),
+            is_const: false,
+        })
+        .collect()
 }
 
-fn clean_fn_decl_with_args<'tcx>(
+fn clean_fn_decl_with_params<'tcx>(
     cx: &mut DocContext<'tcx>,
     decl: &hir::FnDecl<'tcx>,
     header: Option<&hir::FnHeader>,
-    args: Arguments,
+    params: Vec<Parameter>,
 ) -> FnDecl {
     let mut output = match decl.output {
         hir::FnRetTy::Return(typ) => clean_ty(typ, cx),
@@ -1191,7 +1162,7 @@ fn clean_fn_decl_with_args<'tcx>(
     {
         output = output.sugared_async_return_type();
     }
-    FnDecl { inputs: args, output, c_variadic: decl.c_variadic }
+    FnDecl { inputs: params, output, c_variadic: decl.c_variadic }
 }
 
 fn clean_poly_fn_sig<'tcx>(
@@ -1199,10 +1170,6 @@ fn clean_poly_fn_sig<'tcx>(
     did: Option<DefId>,
     sig: ty::PolyFnSig<'tcx>,
 ) -> FnDecl {
-    let mut names = did.map_or(&[] as &[_], |did| cx.tcx.fn_arg_idents(did)).iter();
-
-    // We assume all empty tuples are default return type. This theoretically can discard `-> ()`,
-    // but shouldn't change any code meaning.
     let mut output = clean_middle_ty(sig.output(), cx, None, None);
 
     // If the return type isn't an `impl Trait`, we can safely assume that this
@@ -1215,25 +1182,25 @@ fn clean_poly_fn_sig<'tcx>(
         output = output.sugared_async_return_type();
     }
 
-    FnDecl {
-        output,
-        c_variadic: sig.skip_binder().c_variadic,
-        inputs: Arguments {
-            values: sig
-                .inputs()
-                .iter()
-                .map(|t| Argument {
-                    type_: clean_middle_ty(t.map_bound(|t| *t), cx, None, None),
-                    name: Some(if let Some(Some(ident)) = names.next() {
-                        ident.name
-                    } else {
-                        kw::Underscore
-                    }),
-                    is_const: false,
-                })
-                .collect(),
-        },
-    }
+    let mut idents = did.map(|did| cx.tcx.fn_arg_idents(did)).unwrap_or_default().iter().copied();
+
+    // If this comes from a fn item, let's not perpetuate anon params from Rust 2015; use `_` for them.
+    // If this comes from a fn ptr ty, we just keep params unnamed since it's more conventional stylistically.
+    // Since the param name is not part of the semantic type, these params never bear a name unlike
+    // in the HIR case, thus we can't peform any fancy fallback logic unlike `clean_bare_fn_ty`.
+    let fallback = did.map(|_| kw::Underscore);
+
+    let params = sig
+        .inputs()
+        .iter()
+        .map(|ty| Parameter {
+            name: idents.next().flatten().map(|ident| ident.name).or(fallback),
+            type_: clean_middle_ty(ty.map_bound(|ty| *ty), cx, None, None),
+            is_const: false,
+        })
+        .collect();
+
+    FnDecl { inputs: params, output, c_variadic: sig.skip_binder().c_variadic }
 }
 
 fn clean_trait_ref<'tcx>(trait_ref: &hir::TraitRef<'tcx>, cx: &mut DocContext<'tcx>) -> Path {
@@ -1273,11 +1240,11 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext
                 RequiredAssocConstItem(generics, Box::new(clean_ty(ty, cx)))
             }
             hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => {
-                let m = clean_function(cx, sig, trait_item.generics, FunctionArgs::Body(body));
+                let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Body(body));
                 MethodItem(m, None)
             }
             hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(idents)) => {
-                let m = clean_function(cx, sig, trait_item.generics, FunctionArgs::Idents(idents));
+                let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Idents(idents));
                 RequiredMethodItem(m)
             }
             hir::TraitItemKind::Type(bounds, Some(default)) => {
@@ -1318,7 +1285,7 @@ pub(crate) fn clean_impl_item<'tcx>(
                 type_: clean_ty(ty, cx),
             })),
             hir::ImplItemKind::Fn(ref sig, body) => {
-                let m = clean_function(cx, sig, impl_.generics, FunctionArgs::Body(body));
+                let m = clean_function(cx, sig, impl_.generics, ParamsSrc::Body(body));
                 let defaultness = cx.tcx.defaultness(impl_.owner_id);
                 MethodItem(m, Some(defaultness))
             }
@@ -1390,14 +1357,14 @@ pub(crate) fn clean_middle_assoc_item(assoc_item: &ty::AssocItem, cx: &mut DocCo
                     }
                     ty::AssocItemContainer::Trait => tcx.types.self_param,
                 };
-                let self_arg_ty =
+                let self_param_ty =
                     tcx.fn_sig(assoc_item.def_id).instantiate_identity().input(0).skip_binder();
-                if self_arg_ty == self_ty {
-                    item.decl.inputs.values[0].type_ = SelfTy;
-                } else if let ty::Ref(_, ty, _) = *self_arg_ty.kind()
+                if self_param_ty == self_ty {
+                    item.decl.inputs[0].type_ = SelfTy;
+                } else if let ty::Ref(_, ty, _) = *self_param_ty.kind()
                     && ty == self_ty
                 {
-                    match item.decl.inputs.values[0].type_ {
+                    match item.decl.inputs[0].type_ {
                         BorrowedRef { ref mut type_, .. } => **type_ = SelfTy,
                         _ => unreachable!(),
                     }
@@ -2611,15 +2578,25 @@ fn clean_bare_fn_ty<'tcx>(
     cx: &mut DocContext<'tcx>,
 ) -> BareFunctionDecl {
     let (generic_params, decl) = enter_impl_trait(cx, |cx| {
-        // NOTE: generics must be cleaned before args
+        // NOTE: Generics must be cleaned before params.
         let generic_params = bare_fn
             .generic_params
             .iter()
             .filter(|p| !is_elided_lifetime(p))
             .map(|x| clean_generic_param(cx, None, x))
             .collect();
-        let args = clean_args_from_types_and_names(cx, bare_fn.decl.inputs, bare_fn.param_idents);
-        let decl = clean_fn_decl_with_args(cx, bare_fn.decl, None, args);
+        // Since it's more conventional stylistically, elide the name of all params called `_`
+        // unless there's at least one interestingly named param in which case don't elide any
+        // name since mixing named and unnamed params is less legible.
+        let filter = |ident: Option<Ident>| {
+            ident.map(|ident| ident.name).filter(|&ident| ident != kw::Underscore)
+        };
+        let fallback =
+            bare_fn.param_idents.iter().copied().find_map(filter).map(|_| kw::Underscore);
+        let params = clean_params(cx, bare_fn.decl.inputs, bare_fn.param_idents, |ident| {
+            filter(ident).or(fallback)
+        });
+        let decl = clean_fn_decl_with_params(cx, bare_fn.decl, None, params);
         (generic_params, decl)
     });
     BareFunctionDecl { safety: bare_fn.safety, abi: bare_fn.abi, decl, generic_params }
@@ -2629,7 +2606,6 @@ fn clean_unsafe_binder_ty<'tcx>(
     unsafe_binder_ty: &hir::UnsafeBinderTy<'tcx>,
     cx: &mut DocContext<'tcx>,
 ) -> UnsafeBinderTy {
-    // NOTE: generics must be cleaned before args
     let generic_params = unsafe_binder_ty
         .generic_params
         .iter()
@@ -3155,7 +3131,7 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
     cx.with_param_env(def_id, |cx| {
         let kind = match item.kind {
             hir::ForeignItemKind::Fn(sig, idents, generics) => ForeignFunctionItem(
-                clean_function(cx, &sig, generics, FunctionArgs::Idents(idents)),
+                clean_function(cx, &sig, generics, ParamsSrc::Idents(idents)),
                 sig.header.safety(),
             ),
             hir::ForeignItemKind::Static(ty, mutability, safety) => ForeignStaticItem(
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index f58cdfc6b5e..bbe11bf56af 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -788,7 +788,7 @@ impl Item {
                         }
                         _ => Some(rustc_hir_pretty::attribute_to_string(&tcx, attr)),
                     }
-                } else if ALLOWED_ATTRIBUTES.contains(&attr.name_or_empty()) {
+                } else if attr.has_any_name(ALLOWED_ATTRIBUTES) {
                     Some(
                         rustc_hir_pretty::attribute_to_string(&tcx, attr)
                             .replace("\\\n", "")
@@ -1407,32 +1407,28 @@ pub(crate) struct Function {
 
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
 pub(crate) struct FnDecl {
-    pub(crate) inputs: Arguments,
+    pub(crate) inputs: Vec<Parameter>,
     pub(crate) output: Type,
     pub(crate) c_variadic: bool,
 }
 
 impl FnDecl {
     pub(crate) fn receiver_type(&self) -> Option<&Type> {
-        self.inputs.values.first().and_then(|v| v.to_receiver())
+        self.inputs.first().and_then(|v| v.to_receiver())
     }
 }
 
+/// A function parameter.
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
-pub(crate) struct Arguments {
-    pub(crate) values: Vec<Argument>,
-}
-
-#[derive(Clone, PartialEq, Eq, Debug, Hash)]
-pub(crate) struct Argument {
-    pub(crate) type_: Type,
+pub(crate) struct Parameter {
     pub(crate) name: Option<Symbol>,
+    pub(crate) type_: Type,
     /// This field is used to represent "const" arguments from the `rustc_legacy_const_generics`
     /// feature. More information in <https://github.com/rust-lang/rust/issues/83167>.
     pub(crate) is_const: bool,
 }
 
-impl Argument {
+impl Parameter {
     pub(crate) fn to_receiver(&self) -> Option<&Type> {
         if self.name == Some(kw::SelfLower) { Some(&self.type_) } else { None }
     }
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index 8ee08edec19..af7986d030e 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -303,13 +303,12 @@ pub(crate) fn name_from_pat(p: &hir::Pat<'_>) -> Symbol {
     debug!("trying to get a name from pattern: {p:?}");
 
     Symbol::intern(&match &p.kind {
-        // FIXME(never_patterns): does this make sense?
-        PatKind::Missing => unreachable!(),
-        PatKind::Wild
-        | PatKind::Err(_)
+        PatKind::Err(_)
+        | PatKind::Missing // Let's not perpetuate anon params from Rust 2015; use `_` for them.
         | PatKind::Never
+        | PatKind::Range(..)
         | PatKind::Struct(..)
-        | PatKind::Range(..) => {
+        | PatKind::Wild => {
             return kw::Underscore;
         }
         PatKind::Binding(_, _, ident, _) => return ident.name,
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 41688b41c6e..9d1c9ff00b1 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -412,9 +412,7 @@ pub(crate) fn run_global_ctxt(
     // Process all of the crate attributes, extracting plugin metadata along
     // with the passes which we are supposed to run.
     for attr in krate.module.attrs.lists(sym::doc) {
-        let name = attr.name_or_empty();
-
-        if attr.is_word() && name == sym::document_private_items {
+        if attr.is_word() && attr.has_name(sym::document_private_items) {
             ctxt.render_options.document_private = true;
         }
     }
diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs
index 4edd5433de6..d5c965f7053 100644
--- a/src/librustdoc/doctest/make.rs
+++ b/src/librustdoc/doctest/make.rs
@@ -345,7 +345,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
     fn check_item(item: &ast::Item, info: &mut ParseSourceInfo, crate_name: &Option<&str>) -> bool {
         let mut is_extern_crate = false;
         if !info.has_global_allocator
-            && item.attrs.iter().any(|attr| attr.name_or_empty() == sym::global_allocator)
+            && item.attrs.iter().any(|attr| attr.has_name(sym::global_allocator))
         {
             info.has_global_allocator = true;
         }
@@ -377,7 +377,7 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
     }
 
     let mut prev_span_hi = 0;
-    let not_crate_attrs = [sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect];
+    let not_crate_attrs = &[sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect];
     let parsed = parser.parse_item(rustc_parse::parser::ForceCollect::No);
 
     let result = match parsed {
@@ -386,17 +386,13 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
                 && let Some(ref body) = fn_item.body =>
         {
             for attr in &item.attrs {
-                let attr_name = attr.name_or_empty();
-
-                if attr.style == AttrStyle::Outer || not_crate_attrs.contains(&attr_name) {
+                if attr.style == AttrStyle::Outer || attr.has_any_name(not_crate_attrs) {
                     // There is one exception to these attributes:
                     // `#![allow(internal_features)]`. If this attribute is used, we need to
                     // consider it only as a crate-level attribute.
-                    if attr_name == sym::allow
+                    if attr.has_name(sym::allow)
                         && let Some(list) = attr.meta_item_list()
-                        && list.iter().any(|sub_attr| {
-                            sub_attr.name_or_empty().as_str() == "internal_features"
-                        })
+                        && list.iter().any(|sub_attr| sub_attr.has_name(sym::internal_features))
                     {
                         push_to_s(&mut info.crate_attrs, source, attr.span, &mut prev_span_hi);
                     } else {
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index 9ac328f7495..299fd6b9adb 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -1186,8 +1186,8 @@ impl clean::Impl {
         {
             primitive_link(f, PrimitiveType::Array, format_args!("[{name}; N]"), cx)?;
         } else if let clean::BareFunction(bare_fn) = &type_
-            && let [clean::Argument { type_: clean::Type::Generic(name), .. }] =
-                &bare_fn.decl.inputs.values[..]
+            && let [clean::Parameter { type_: clean::Type::Generic(name), .. }] =
+                &bare_fn.decl.inputs[..]
             && (self.kind.is_fake_variadic() || self.kind.is_auto())
         {
             // Hardcoded anchor library/core/src/primitive_docs.rs
@@ -1234,22 +1234,20 @@ impl clean::Impl {
     }
 }
 
-impl clean::Arguments {
-    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
-        fmt::from_fn(move |f| {
-            self.values
-                .iter()
-                .map(|input| {
-                    fmt::from_fn(|f| {
-                        if let Some(name) = input.name {
-                            write!(f, "{}: ", name)?;
-                        }
-                        input.type_.print(cx).fmt(f)
-                    })
+pub(crate) fn print_params(params: &[clean::Parameter], cx: &Context<'_>) -> impl Display {
+    fmt::from_fn(move |f| {
+        params
+            .iter()
+            .map(|param| {
+                fmt::from_fn(|f| {
+                    if let Some(name) = param.name {
+                        write!(f, "{}: ", name)?;
+                    }
+                    param.type_.print(cx).fmt(f)
                 })
-                .joined(", ", f)
-        })
-    }
+            })
+            .joined(", ", f)
+    })
 }
 
 // Implements Write but only counts the bytes "written".
@@ -1281,16 +1279,16 @@ impl clean::FnDecl {
             if f.alternate() {
                 write!(
                     f,
-                    "({args:#}{ellipsis}){arrow:#}",
-                    args = self.inputs.print(cx),
+                    "({params:#}{ellipsis}){arrow:#}",
+                    params = print_params(&self.inputs, cx),
                     ellipsis = ellipsis,
                     arrow = self.print_output(cx)
                 )
             } else {
                 write!(
                     f,
-                    "({args}{ellipsis}){arrow}",
-                    args = self.inputs.print(cx),
+                    "({params}{ellipsis}){arrow}",
+                    params = print_params(&self.inputs, cx),
                     ellipsis = ellipsis,
                     arrow = self.print_output(cx)
                 )
@@ -1336,14 +1334,14 @@ impl clean::FnDecl {
 
         write!(f, "(")?;
         if let Some(n) = line_wrapping_indent
-            && !self.inputs.values.is_empty()
+            && !self.inputs.is_empty()
         {
             write!(f, "\n{}", Indent(n + 4))?;
         }
 
-        let last_input_index = self.inputs.values.len().checked_sub(1);
-        for (i, input) in self.inputs.values.iter().enumerate() {
-            if let Some(selfty) = input.to_receiver() {
+        let last_input_index = self.inputs.len().checked_sub(1);
+        for (i, param) in self.inputs.iter().enumerate() {
+            if let Some(selfty) = param.to_receiver() {
                 match selfty {
                     clean::SelfTy => {
                         write!(f, "self")?;
@@ -1361,13 +1359,13 @@ impl clean::FnDecl {
                     }
                 }
             } else {
-                if input.is_const {
+                if param.is_const {
                     write!(f, "const ")?;
                 }
-                if let Some(name) = input.name {
+                if let Some(name) = param.name {
                     write!(f, "{}: ", name)?;
                 }
-                input.type_.print(cx).fmt(f)?;
+                param.type_.print(cx).fmt(f)?;
             }
             match (line_wrapping_indent, last_input_index) {
                 (_, None) => (),
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 596ac665fc3..f22935df96c 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -521,23 +521,23 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         // Crawl the crate attributes looking for attributes which control how we're
         // going to emit HTML
         for attr in krate.module.attrs.lists(sym::doc) {
-            match (attr.name_or_empty(), attr.value_str()) {
-                (sym::html_favicon_url, Some(s)) => {
+            match (attr.name(), attr.value_str()) {
+                (Some(sym::html_favicon_url), Some(s)) => {
                     layout.favicon = s.to_string();
                 }
-                (sym::html_logo_url, Some(s)) => {
+                (Some(sym::html_logo_url), Some(s)) => {
                     layout.logo = s.to_string();
                 }
-                (sym::html_playground_url, Some(s)) => {
+                (Some(sym::html_playground_url), Some(s)) => {
                     playground = Some(markdown::Playground {
                         crate_name: Some(krate.name(tcx)),
                         url: s.to_string(),
                     });
                 }
-                (sym::issue_tracker_base_url, Some(s)) => {
+                (Some(sym::issue_tracker_base_url), Some(s)) => {
                     issue_tracker_base_url = Some(s.to_string());
                 }
-                (sym::html_no_source, None) if attr.is_word() => {
+                (Some(sym::html_no_source), None) if attr.is_word() => {
                     include_sources = false;
                 }
                 _ => {}
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index 1360ab94cb1..aff8684ee3a 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -1112,7 +1112,7 @@ fn simplify_fn_type<'a, 'tcx>(
         }
         Type::BareFunction(ref bf) => {
             let mut ty_generics = Vec::new();
-            for ty in bf.decl.inputs.values.iter().map(|arg| &arg.type_) {
+            for ty in bf.decl.inputs.iter().map(|arg| &arg.type_) {
                 simplify_fn_type(
                     self_,
                     generics,
@@ -1418,15 +1418,15 @@ fn get_fn_inputs_and_outputs(
         (None, &func.generics)
     };
 
-    let mut arg_types = Vec::new();
-    for arg in decl.inputs.values.iter() {
+    let mut param_types = Vec::new();
+    for param in decl.inputs.iter() {
         simplify_fn_type(
             self_,
             generics,
-            &arg.type_,
+            &param.type_,
             tcx,
             0,
-            &mut arg_types,
+            &mut param_types,
             &mut rgen,
             false,
             cache,
@@ -1439,7 +1439,7 @@ fn get_fn_inputs_and_outputs(
     let mut simplified_params = rgen.into_iter().collect::<Vec<_>>();
     simplified_params.sort_by_key(|(_, (idx, _))| -idx);
     (
-        arg_types,
+        param_types,
         ret_types,
         simplified_params
             .iter()
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 5d85a4676b7..dab23f8e42a 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -609,11 +609,12 @@ impl FromClean<clean::FnDecl> for FunctionSignature {
         let clean::FnDecl { inputs, output, c_variadic } = decl;
         FunctionSignature {
             inputs: inputs
-                .values
                 .into_iter()
-                // `_` is the most sensible name for missing param names.
-                .map(|arg| {
-                    (arg.name.unwrap_or(kw::Underscore).to_string(), arg.type_.into_json(renderer))
+                .map(|param| {
+                    // `_` is the most sensible name for missing param names.
+                    let name = param.name.unwrap_or(kw::Underscore).to_string();
+                    let type_ = param.type_.into_json(renderer);
+                    (name, type_)
                 })
                 .collect(),
             output: if output.is_unit() { None } else { Some(output.into_json(renderer)) },
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index ba27eed7c11..131a12ce228 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -14,6 +14,7 @@ use std::io::{BufWriter, Write, stdout};
 use std::path::PathBuf;
 use std::rc::Rc;
 
+use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def_id::{DefId, DefIdSet};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
@@ -123,6 +124,58 @@ impl<'tcx> JsonRenderer<'tcx> {
     }
 }
 
+fn target(sess: &rustc_session::Session) -> types::Target {
+    // Build a set of which features are enabled on this target
+    let globally_enabled_features: FxHashSet<&str> =
+        sess.unstable_target_features.iter().map(|name| name.as_str()).collect();
+
+    // Build a map of target feature stability by feature name
+    use rustc_target::target_features::Stability;
+    let feature_stability: FxHashMap<&str, Stability> = sess
+        .target
+        .rust_target_features()
+        .into_iter()
+        .copied()
+        .map(|(name, stability, _)| (name, stability))
+        .collect();
+
+    types::Target {
+        triple: sess.opts.target_triple.tuple().into(),
+        target_features: sess
+            .target
+            .rust_target_features()
+            .into_iter()
+            .copied()
+            .filter(|(_, stability, _)| {
+                // Describe only target features which the user can toggle
+                stability.toggle_allowed().is_ok()
+            })
+            .map(|(name, stability, implied_features)| {
+                types::TargetFeature {
+                    name: name.into(),
+                    unstable_feature_gate: match stability {
+                        Stability::Unstable(feature_gate) => Some(feature_gate.as_str().into()),
+                        _ => None,
+                    },
+                    implies_features: implied_features
+                        .into_iter()
+                        .copied()
+                        .filter(|name| {
+                            // Imply only target features which the user can toggle
+                            feature_stability
+                                .get(name)
+                                .map(|stability| stability.toggle_allowed().is_ok())
+                                .unwrap_or(false)
+                        })
+                        .map(String::from)
+                        .collect(),
+                    globally_enabled: globally_enabled_features.contains(name),
+                }
+            })
+            .collect(),
+    }
+}
+
 impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
     fn descr() -> &'static str {
         "json"
@@ -248,6 +301,12 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
         let e = ExternalCrate { crate_num: LOCAL_CRATE };
         let index = (*self.index).clone().into_inner();
 
+        // 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
+        //
+        // We want to describe a single target, so pass tcx.sess rather than tcx.
+        let target = target(self.tcx.sess);
+
         debug!("Constructing Output");
         let output_crate = types::Crate {
             root: self.id_from_item_default(e.def_id().into()),
@@ -288,6 +347,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
                     )
                 })
                 .collect(),
+            target,
             format_version: types::FORMAT_VERSION,
         };
         if let Some(ref out_dir) = self.out_dir {
diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs
index 137fe4c4c35..7247950545a 100644
--- a/src/rustdoc-json-types/lib.rs
+++ b/src/rustdoc-json-types/lib.rs
@@ -30,7 +30,7 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
 /// This integer is incremented with every breaking change to the API,
 /// and is returned along with the JSON blob as [`Crate::format_version`].
 /// Consuming code should assert that this value matches the format version(s) that it supports.
-pub const FORMAT_VERSION: u32 = 43;
+pub const FORMAT_VERSION: u32 = 44;
 
 /// The root of the emitted JSON blob.
 ///
@@ -52,11 +52,67 @@ pub struct Crate {
     pub paths: HashMap<Id, ItemSummary>,
     /// Maps `crate_id` of items to a crate name and html_root_url if it exists.
     pub external_crates: HashMap<u32, ExternalCrate>,
+    /// Information about the target for which this documentation was generated
+    pub target: Target,
     /// A single version number to be used in the future when making backwards incompatible changes
     /// to the JSON output.
     pub format_version: u32,
 }
 
+/// Information about a target
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Target {
+    /// The target triple for which this documentation was generated
+    pub triple: String,
+    /// A list of features valid for use in `#[target_feature]` attributes
+    /// for the target where this rustdoc JSON was generated.
+    pub target_features: Vec<TargetFeature>,
+}
+
+/// Information about a target feature.
+///
+/// Rust target features are used to influence code generation, especially around selecting
+/// instructions which are not universally supported by the target architecture.
+///
+/// Target features are commonly enabled by the [`#[target_feature]` attribute][1] to influence code
+/// generation for a particular function, and less commonly enabled by compiler options like
+/// `-Ctarget-feature` or `-Ctarget-cpu`. Targets themselves automatically enable certain target
+/// features by default, for example because the target's ABI specification requires saving specific
+/// registers which only exist in an architectural extension.
+///
+/// Target features can imply other target features: for example, x86-64 `avx2` implies `avx`, and
+/// aarch64 `sve2` implies `sve`, since both of these architectural extensions depend on their
+/// predecessors.
+///
+/// Target features can be probed at compile time by [`#[cfg(target_feature)]`][2] or `cfg!(…)`
+/// conditional compilation to determine whether a target feature is enabled in a particular
+/// context.
+///
+/// [1]: https://doc.rust-lang.org/stable/reference/attributes/codegen.html#the-target_feature-attribute
+/// [2]: https://doc.rust-lang.org/reference/conditional-compilation.html#target_feature
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct TargetFeature {
+    /// The name of this target feature.
+    pub name: String,
+    /// Other target features which are implied by this target feature, if any.
+    pub implies_features: Vec<String>,
+    /// If this target feature is unstable, the name of the associated language feature gate.
+    pub unstable_feature_gate: Option<String>,
+    /// Whether this feature is globally enabled for this compilation session.
+    ///
+    /// Target features can be globally enabled implicitly as a result of the target's definition.
+    /// For example, x86-64 hardware floating point ABIs require saving x87 and SSE2 registers,
+    /// which in turn requires globally enabling the `x87` and `sse2` target features so that the
+    /// generated machine code conforms to the target's ABI.
+    ///
+    /// Target features can also be globally enabled explicitly as a result of compiler flags like
+    /// [`-Ctarget-feature`][1] or [`-Ctarget-cpu`][2].
+    ///
+    /// [1]: https://doc.rust-lang.org/beta/rustc/codegen-options/index.html#target-feature
+    /// [2]: https://doc.rust-lang.org/beta/rustc/codegen-options/index.html#target-cpu
+    pub globally_enabled: bool,
+}
+
 /// Metadata of a crate, either the same crate on which `rustdoc` was invoked, or its dependency.
 #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
 pub struct ExternalCrate {
diff --git a/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs b/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs
index 8f1a1ee76c6..96d3f7196c0 100644
--- a/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs
+++ b/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs
@@ -179,7 +179,7 @@ fn find_first_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(Span, Mut
         };
         if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) {
             if let [first, ..] = **adjustments {
-                if let ty::Ref(.., mutability) = *first.kind() {
+                if let ty::Ref(.., mutability) = *first.source.kind() {
                     let level = if p.hir_id == pat.hir_id {
                         Level::Top
                     } else {
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index 9f450d654d5..f715fc86e4e 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -2363,14 +2363,14 @@ pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
     cx.tcx
         .hir_attrs(hir::CRATE_HIR_ID)
         .iter()
-        .any(|attr| attr.name_or_empty() == sym::no_std)
+        .any(|attr| attr.has_name(sym::no_std))
 }
 
 pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool {
     cx.tcx
         .hir_attrs(hir::CRATE_HIR_ID)
         .iter()
-        .any(|attr| attr.name_or_empty() == sym::no_core)
+        .any(|attr| attr.has_name(sym::no_core))
 }
 
 /// Check if parent of a hir node is a trait implementation block.
diff --git a/src/tools/compiletest/src/directive-list.rs b/src/tools/compiletest/src/directive-list.rs
index 44c52c8c766..1449e9af19a 100644
--- a/src/tools/compiletest/src/directive-list.rs
+++ b/src/tools/compiletest/src/directive-list.rs
@@ -178,6 +178,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "only-32bit",
     "only-64bit",
     "only-aarch64",
+    "only-aarch64-apple-darwin",
     "only-aarch64-unknown-linux-gnu",
     "only-apple",
     "only-arm",
@@ -191,6 +192,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "only-gnu",
     "only-i686-pc-windows-gnu",
     "only-i686-pc-windows-msvc",
+    "only-i686-unknown-linux-gnu",
     "only-ios",
     "only-linux",
     "only-loongarch64",
@@ -222,6 +224,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "only-windows-msvc",
     "only-x86",
     "only-x86_64",
+    "only-x86_64-apple-darwin",
     "only-x86_64-fortanix-unknown-sgx",
     "only-x86_64-pc-windows-gnu",
     "only-x86_64-pc-windows-msvc",
diff --git a/src/tools/jsondocck/src/main.rs b/src/tools/jsondocck/src/main.rs
index 54249fbd9ae..79e419884c6 100644
--- a/src/tools/jsondocck/src/main.rs
+++ b/src/tools/jsondocck/src/main.rs
@@ -156,7 +156,7 @@ static LINE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
         r#"
         //@\s+
         (?P<negated>!?)
-        (?P<cmd>[A-Za-z]+(?:-[A-Za-z]+)*)
+        (?P<cmd>[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)
         (?P<args>.*)$
     "#,
     )
diff --git a/src/tools/jsondoclint/src/validator/tests.rs b/src/tools/jsondoclint/src/validator/tests.rs
index 28deb7e7cee..dd0b4ac5601 100644
--- a/src/tools/jsondoclint/src/validator/tests.rs
+++ b/src/tools/jsondoclint/src/validator/tests.rs
@@ -42,6 +42,7 @@ fn errors_on_missing_links() {
         )]),
         paths: FxHashMap::default(),
         external_crates: FxHashMap::default(),
+        target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] },
         format_version: rustdoc_json_types::FORMAT_VERSION,
     };
 
@@ -112,6 +113,7 @@ fn errors_on_local_in_paths_and_not_index() {
             },
         )]),
         external_crates: FxHashMap::default(),
+        target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] },
         format_version: rustdoc_json_types::FORMAT_VERSION,
     };
 
@@ -216,6 +218,7 @@ fn errors_on_missing_path() {
             ItemSummary { crate_id: 0, path: vec!["foo".to_owned()], kind: ItemKind::Module },
         )]),
         external_crates: FxHashMap::default(),
+        target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] },
         format_version: rustdoc_json_types::FORMAT_VERSION,
     };
 
@@ -259,6 +262,7 @@ fn checks_local_crate_id_is_correct() {
         )]),
         paths: FxHashMap::default(),
         external_crates: FxHashMap::default(),
+        target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] },
         format_version: FORMAT_VERSION,
     };
     check(&krate, &[]);
diff --git a/src/tools/miri/tests/pass/issues/issue-139553.rs b/src/tools/miri/tests/pass/issues/issue-139553.rs
new file mode 100644
index 00000000000..119d589d1ea
--- /dev/null
+++ b/src/tools/miri/tests/pass/issues/issue-139553.rs
@@ -0,0 +1,45 @@
+//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-compare-exchange-weak-failure-rate=0
+use std::sync::mpsc::channel;
+use std::thread;
+
+/// This test aims to trigger a race condition that causes a double free in the unbounded channel
+/// implementation. The test relies on a particular thread scheduling to happen as annotated by the
+/// comments below.
+fn main() {
+    let (s1, r) = channel::<u64>();
+    let s2 = s1.clone();
+
+    let t1 = thread::spawn(move || {
+        // 1. The first action executed is an attempt to send the first value in the channel. This
+        //    will begin to initialize the channel but will stop at a critical momement as
+        //    indicated by the `yield_now()` call in the `start_send` method of the implementation.
+        let _ = s1.send(42);
+        // 4. The sender is re-scheduled and it finishes the initialization of the channel by
+        //    setting head.block to the same value as tail.block. It then proceeds to publish its
+        //    value but observes that the channel has already disconnected (due to the concurrent
+        //    call of `discard_all_messages`) and aborts the send.
+    });
+    std::thread::yield_now();
+
+    // 2. A second sender attempts to send a value while the channel is in a half-initialized
+    //    state. Here, half-initialized means that the `tail.block` pointer points to a valid block
+    //    but `head.block` is still null. This condition is ensured by the yield of step 1. When
+    //    this call returns the channel state has tail.index != head.index, tail.block != NULL, and
+    //    head.block = NULL.
+    s2.send(42).unwrap();
+    // 3. This thread continues with dropping the one and only receiver. When all receivers are
+    //    gone `discard_all_messages` will attempt to drop all currently sent values and
+    //    de-allocate all the blocks. If `tail.block != NULL` but `head.block = NULL` the
+    //    implementation waits for the initializing sender to finish by spinning/yielding.
+    drop(r);
+    // 5. This thread is rescheduled and `discard_all_messages` observes the head.block pointer set
+    //    by step 4 and proceeds with deallocation. In the problematic version of the code
+    //    `head.block` is simply read via an `Acquire` load and not swapped with NULL. After this
+    //    call returns the channel state has tail.index = head.index, tail.block = NULL, and
+    //    head.block != NULL.
+    t1.join().unwrap();
+    // 6. The last sender (s2) is dropped here which also attempts to cleanup any data in the
+    //    channel. It observes `tail.index = head.index` and so it doesn't attempt to cleanup any
+    //    messages but it also observes that `head.block != NULL` and attempts to deallocate it.
+    //    This is however already deallocated by `discard_all_messages`, leading to a double free.
+}