From 32cb8f1537a3c0948e01cb90c850d9f60e3139ed Mon Sep 17 00:00:00 2001 From: "Deven T. Corzine" Date: Tue, 10 Jun 2025 23:50:48 -0400 Subject: [PATCH 01/14] Add trim_prefix and trim_suffix for slice and str. Implements `trim_prefix` and `trim_suffix` methods for both `slice` and `str` types which remove at most one occurrence of a prefix/suffix while always returning a string/slice (rather than Option), enabling easy method chaining. --- library/core/src/slice/mod.rs | 83 +++++++++++++++++++++++++++++++++++ library/core/src/str/mod.rs | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 4f7e144088045..bdd83a2bd33c3 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -2763,6 +2763,89 @@ impl [T] { None } + /// Returns a subslice with the optional prefix removed. + /// + /// If the slice starts with `prefix`, returns the subslice after the prefix. If `prefix` + /// is empty or the slice does not start with `prefix`, simply returns the original slice. + /// If `prefix` is equal to the original slice, returns an empty slice. + /// + /// # Examples + /// + /// ``` + /// #![feature(trim_prefix_suffix)] + /// + /// let v = &[10, 40, 30]; + /// + /// // Prefix present - removes it + /// assert_eq!(v.trim_prefix(&[10]), &[40, 30][..]); + /// assert_eq!(v.trim_prefix(&[10, 40]), &[30][..]); + /// assert_eq!(v.trim_prefix(&[10, 40, 30]), &[][..]); + /// + /// // Prefix absent - returns original slice + /// assert_eq!(v.trim_prefix(&[50]), &[10, 40, 30][..]); + /// assert_eq!(v.trim_prefix(&[10, 50]), &[10, 40, 30][..]); + /// + /// let prefix : &str = "he"; + /// assert_eq!(b"hello".trim_prefix(prefix.as_bytes()), b"llo".as_ref()); + /// ``` + #[must_use = "returns the subslice without modifying the original"] + #[unstable(feature = "trim_prefix_suffix", issue = "142312")] + pub fn trim_prefix + ?Sized>(&self, prefix: &P) -> &[T] + where + T: PartialEq, + { + // This function will need rewriting if and when SlicePattern becomes more sophisticated. + let prefix = prefix.as_slice(); + let n = prefix.len(); + if n <= self.len() { + let (head, tail) = self.split_at(n); + if head == prefix { + return tail; + } + } + self + } + + /// Returns a subslice with the optional suffix removed. + /// + /// If the slice ends with `suffix`, returns the subslice before the suffix. If `suffix` + /// is empty or the slice does not end with `suffix`, simply returns the original slice. + /// If `suffix` is equal to the original slice, returns an empty slice. + /// + /// # Examples + /// + /// ``` + /// #![feature(trim_prefix_suffix)] + /// + /// let v = &[10, 40, 30]; + /// + /// // Suffix present - removes it + /// assert_eq!(v.trim_suffix(&[30]), &[10, 40][..]); + /// assert_eq!(v.trim_suffix(&[40, 30]), &[10][..]); + /// assert_eq!(v.trim_suffix(&[10, 40, 30]), &[][..]); + /// + /// // Suffix absent - returns original slice + /// assert_eq!(v.trim_suffix(&[50]), &[10, 40, 30][..]); + /// assert_eq!(v.trim_suffix(&[50, 30]), &[10, 40, 30][..]); + /// ``` + #[must_use = "returns the subslice without modifying the original"] + #[unstable(feature = "trim_prefix_suffix", issue = "142312")] + pub fn trim_suffix + ?Sized>(&self, suffix: &P) -> &[T] + where + T: PartialEq, + { + // This function will need rewriting if and when SlicePattern becomes more sophisticated. + let suffix = suffix.as_slice(); + let (len, n) = (self.len(), suffix.len()); + if n <= len { + let (head, tail) = self.split_at(len - n); + if tail == suffix { + return head; + } + } + self + } + /// Binary searches this slice for a given element. /// If the slice is not sorted, the returned result is unspecified and /// meaningless. diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 41834793d22a5..5051b2288fd2b 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -2426,6 +2426,83 @@ impl str { suffix.strip_suffix_of(self) } + /// Returns a string slice with the optional prefix removed. + /// + /// If the string starts with the pattern `prefix`, returns the substring after the prefix. + /// Unlike [`strip_prefix`], this method always returns `&str` for easy method chaining, + /// instead of returning [`Option<&str>`]. + /// + /// If the string does not start with `prefix`, returns the original string unchanged. + /// + /// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a + /// function or closure that determines if a character matches. + /// + /// [`char`]: prim@char + /// [pattern]: self::pattern + /// [`strip_prefix`]: Self::strip_prefix + /// + /// # Examples + /// + /// ``` + /// #![feature(trim_prefix_suffix)] + /// + /// // Prefix present - removes it + /// assert_eq!("foo:bar".trim_prefix("foo:"), "bar"); + /// assert_eq!("foofoo".trim_prefix("foo"), "foo"); + /// + /// // Prefix absent - returns original string + /// assert_eq!("foo:bar".trim_prefix("bar"), "foo:bar"); + /// + /// // Method chaining example + /// assert_eq!("".trim_prefix('<').trim_suffix('>'), "https://example.com/"); + /// ``` + #[must_use = "this returns the remaining substring as a new slice, \ + without modifying the original"] + #[unstable(feature = "trim_prefix_suffix", issue = "142312")] + pub fn trim_prefix(&self, prefix: P) -> &str { + prefix.strip_prefix_of(self).unwrap_or(self) + } + + /// Returns a string slice with the optional suffix removed. + /// + /// If the string ends with the pattern `suffix`, returns the substring before the suffix. + /// Unlike [`strip_suffix`], this method always returns `&str` for easy method chaining, + /// instead of returning [`Option<&str>`]. + /// + /// If the string does not end with `suffix`, returns the original string unchanged. + /// + /// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a + /// function or closure that determines if a character matches. + /// + /// [`char`]: prim@char + /// [pattern]: self::pattern + /// [`strip_suffix`]: Self::strip_suffix + /// + /// # Examples + /// + /// ``` + /// #![feature(trim_prefix_suffix)] + /// + /// // Suffix present - removes it + /// assert_eq!("bar:foo".trim_suffix(":foo"), "bar"); + /// assert_eq!("foofoo".trim_suffix("foo"), "foo"); + /// + /// // Suffix absent - returns original string + /// assert_eq!("bar:foo".trim_suffix("bar"), "bar:foo"); + /// + /// // Method chaining example + /// assert_eq!("".trim_prefix('<').trim_suffix('>'), "https://example.com/"); + /// ``` + #[must_use = "this returns the remaining substring as a new slice, \ + without modifying the original"] + #[unstable(feature = "trim_prefix_suffix", issue = "142312")] + pub fn trim_suffix(&self, suffix: P) -> &str + where + for<'a> P::Searcher<'a>: ReverseSearcher<'a>, + { + suffix.strip_suffix_of(self).unwrap_or(self) + } + /// Returns a string slice with all suffixes that match a pattern /// repeatedly removed. /// From b3914945ae2d77770c25ed124a8450d82c8f7fe9 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Wed, 18 Jun 2025 00:38:55 -0400 Subject: [PATCH 02/14] add ChildExt(::send_signal) --- library/std/src/os/unix/mod.rs | 3 ++ library/std/src/os/unix/process.rs | 35 +++++++++++++++++++ library/std/src/sys/pal/unix/linux/pidfd.rs | 6 +++- library/std/src/sys/process/unix/fuchsia.rs | 5 +++ library/std/src/sys/process/unix/unix.rs | 12 ++++--- .../std/src/sys/process/unix/unsupported.rs | 6 +++- library/std/src/sys/process/unix/vxworks.rs | 8 +++-- 7 files changed, 67 insertions(+), 8 deletions(-) diff --git a/library/std/src/os/unix/mod.rs b/library/std/src/os/unix/mod.rs index 5802b6539651c..78c957270c451 100644 --- a/library/std/src/os/unix/mod.rs +++ b/library/std/src/os/unix/mod.rs @@ -116,6 +116,9 @@ pub mod prelude { #[stable(feature = "rust1", since = "1.0.0")] pub use super::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; #[doc(no_inline)] + #[unstable(feature = "unix_send_signal", issue = "141975")] + pub use super::process::ChildExt; + #[doc(no_inline)] #[stable(feature = "rust1", since = "1.0.0")] pub use super::process::{CommandExt, ExitStatusExt}; #[doc(no_inline)] diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index 57ce3c5a4bf4a..3f4fe2e56ec31 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -378,6 +378,41 @@ impl ExitStatusExt for process::ExitStatusError { } } +#[unstable(feature = "unix_send_signal", issue = "141975")] +pub trait ChildExt: Sealed { + /// Sends a signal to a child process. + /// + /// # Errors + /// + /// This function will return an error if the signal is invalid. The integer values associated + /// with signals are implemenation-specific, so it's encouraged to use a crate that provides + /// posix bindings. + /// + /// # Examples + /// + /// ```rust + /// #![feature(unix_send_signal)] + /// + /// use std::{io, os::unix::process::ChildExt, process::{Command, Stdio}}; + /// + /// use libc::SIGTERM; + /// + /// fn main() -> io::Result<()> { + /// let child = Command::new("cat").stdin(Stdio::piped()).spawn()?; + /// child.send_signal(SIGTERM)?; + /// Ok(()) + /// } + /// ``` + fn send_signal(&self, signal: i32) -> io::Result<()>; +} + +#[unstable(feature = "unix_send_signal", issue = "141975")] +impl ChildExt for process::Child { + fn send_signal(&self, signal: i32) -> io::Result<()> { + self.handle.send_signal(signal) + } +} + #[stable(feature = "process_extensions", since = "1.2.0")] impl FromRawFd for process::Stdio { #[inline] diff --git a/library/std/src/sys/pal/unix/linux/pidfd.rs b/library/std/src/sys/pal/unix/linux/pidfd.rs index 2d949ec9e91f7..47e9a616bfdaf 100644 --- a/library/std/src/sys/pal/unix/linux/pidfd.rs +++ b/library/std/src/sys/pal/unix/linux/pidfd.rs @@ -13,11 +13,15 @@ pub(crate) struct PidFd(FileDesc); impl PidFd { pub fn kill(&self) -> io::Result<()> { + self.send_signal(libc::SIGKILL) + } + + pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> { cvt(unsafe { libc::syscall( libc::SYS_pidfd_send_signal, self.0.as_raw_fd(), - libc::SIGKILL, + signal, crate::ptr::null::<()>(), 0, ) diff --git a/library/std/src/sys/process/unix/fuchsia.rs b/library/std/src/sys/process/unix/fuchsia.rs index 017ab91797ce6..d71be510b6afe 100644 --- a/library/std/src/sys/process/unix/fuchsia.rs +++ b/library/std/src/sys/process/unix/fuchsia.rs @@ -152,6 +152,11 @@ impl Process { Ok(()) } + pub fn send_signal(&self, _signal: i32) -> io::Result<()> { + // Fuchsia doesn't have a direct equivalent for signals + unimplemented!() + } + pub fn wait(&mut self) -> io::Result { let mut proc_info: zx_info_process_t = Default::default(); let mut actual: size_t = 0; diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs index 4f595ac9a1c5f..1fe80c13b81c8 100644 --- a/library/std/src/sys/process/unix/unix.rs +++ b/library/std/src/sys/process/unix/unix.rs @@ -963,9 +963,13 @@ impl Process { self.pid as u32 } - pub fn kill(&mut self) -> io::Result<()> { + pub fn kill(&self) -> io::Result<()> { + self.send_signal(libc::SIGKILL) + } + + pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> { // If we've already waited on this process then the pid can be recycled - // and used for another process, and we probably shouldn't be killing + // and used for another process, and we probably shouldn't be signaling // random processes, so return Ok because the process has exited already. if self.status.is_some() { return Ok(()); @@ -973,9 +977,9 @@ impl Process { #[cfg(target_os = "linux")] if let Some(pid_fd) = self.pidfd.as_ref() { // pidfd_send_signal predates pidfd_open. so if we were able to get an fd then sending signals will work too - return pid_fd.kill(); + return pid_fd.send_signal(signal); } - cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) + cvt(unsafe { libc::kill(self.pid, signal) }).map(drop) } pub fn wait(&mut self) -> io::Result { diff --git a/library/std/src/sys/process/unix/unsupported.rs b/library/std/src/sys/process/unix/unsupported.rs index e86561a5c5c4f..87403cd50f822 100644 --- a/library/std/src/sys/process/unix/unsupported.rs +++ b/library/std/src/sys/process/unix/unsupported.rs @@ -40,7 +40,11 @@ impl Process { 0 } - pub fn kill(&mut self) -> io::Result<()> { + pub fn kill(&self) -> io::Result<()> { + unsupported() + } + + pub fn send_signal(&self, _signal: i32) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs index f33b4a375da83..51ae8c56bdb9b 100644 --- a/library/std/src/sys/process/unix/vxworks.rs +++ b/library/std/src/sys/process/unix/vxworks.rs @@ -146,14 +146,18 @@ impl Process { self.pid as u32 } - pub fn kill(&mut self) -> io::Result<()> { + pub fn kill(&self) -> io::Result<()> { + self.send_signal(libc::SIGKILL) + } + + pub fn send_signal(&self, signal: i32) -> io::Result<()> { // If we've already waited on this process then the pid can be recycled // and used for another process, and we probably shouldn't be killing // random processes, so return Ok because the process has exited already. if self.status.is_some() { Ok(()) } else { - cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) + cvt(unsafe { libc::kill(self.pid, signal) }).map(drop) } } From c6e77b3ba63cc9f824f99b7d356055e22bcf2ae2 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Wed, 18 Jun 2025 15:14:48 +0000 Subject: [PATCH 03/14] Reduce uses of `hir_crate`. --- compiler/rustc_driver_impl/src/pretty.rs | 6 +++++- compiler/rustc_interface/src/passes.rs | 2 +- compiler/rustc_lint/src/expect.rs | 3 +-- compiler/rustc_middle/src/hir/map.rs | 16 ++++++++-------- compiler/rustc_middle/src/hir/mod.rs | 10 +++++++--- compiler/rustc_middle/src/ty/context.rs | 2 +- src/librustdoc/core.rs | 2 -- tests/ui/consts/const-unstable-intrinsic.stderr | 10 ++-------- ...stability-attribute-implies-no-feature.stderr | 5 +---- .../const-traits/staged-api-user-crate.stderr | 5 +---- 10 files changed, 27 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index ec77043cd1287..688307a941f30 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -292,7 +292,11 @@ pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) { } HirTree => { debug!("pretty printing HIR tree"); - format!("{:#?}", ex.tcx().hir_crate(())) + ex.tcx() + .hir_crate_items(()) + .owners() + .map(|owner| format!("{:#?} => {:#?}\n", owner, ex.tcx().hir_owner_nodes(owner))) + .collect() } Mir => { let mut out = Vec::new(); diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 0238d6a39475f..d29656fedd5b4 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1011,7 +1011,7 @@ fn run_required_analyses(tcx: TyCtxt<'_>) { // Prefetch this to prevent multiple threads from blocking on it later. // This is needed since the `hir_id_validator::check_crate` call above is not guaranteed // to use `hir_crate`. - tcx.ensure_done().hir_crate(()); + tcx.ensure_done().hir_crate_items(()); let sess = tcx.sess; sess.time("misc_checking_1", || { diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs index 4c2b82a9a23a6..481e116d06e01 100644 --- a/compiler/rustc_lint/src/expect.rs +++ b/compiler/rustc_lint/src/expect.rs @@ -1,5 +1,4 @@ use rustc_data_structures::fx::FxHashSet; -use rustc_hir::CRATE_OWNER_ID; use rustc_middle::lint::LintExpectation; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; @@ -18,7 +17,7 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp let mut expectations = Vec::new(); - for owner in std::iter::once(CRATE_OWNER_ID).chain(krate.owners()) { + for owner in krate.owners() { let lints = tcx.shallow_lint_levels_on(owner); expectations.extend_from_slice(&lints.expectations); } diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index 3de97c8c0d99e..f75d75dd5b06e 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -328,8 +328,7 @@ impl<'tcx> TyCtxt<'tcx> { } /// Returns an iterator of the `DefId`s for all body-owners in this - /// crate. If you would prefer to iterate over the bodies - /// themselves, you can do `self.hir_crate(()).body_ids.iter()`. + /// crate. #[inline] pub fn hir_body_owners(self) -> impl Iterator { self.hir_crate_items(()).body_owners.iter().copied() @@ -396,12 +395,11 @@ impl<'tcx> TyCtxt<'tcx> { where V: Visitor<'tcx>, { - let krate = self.hir_crate(()); - for info in krate.owners.iter() { - if let MaybeOwner::Owner(info) = info { - for attrs in info.attrs.map.values() { - walk_list!(visitor, visit_attribute, *attrs); - } + let krate = self.hir_crate_items(()); + for owner in krate.owners() { + let attrs = self.hir_attr_map(owner); + for attrs in attrs.map.values() { + walk_list!(visitor, visit_attribute, *attrs); } } V::Result::output() @@ -1225,6 +1223,7 @@ pub(super) fn hir_module_items(tcx: TyCtxt<'_>, module_id: LocalModDefId) -> Mod .. } = collector; ModuleItems { + add_root: false, submodules: submodules.into_boxed_slice(), free_items: items.into_boxed_slice(), trait_items: trait_items.into_boxed_slice(), @@ -1258,6 +1257,7 @@ pub(crate) fn hir_crate_items(tcx: TyCtxt<'_>, _: ()) -> ModuleItems { } = collector; ModuleItems { + add_root: true, submodules: submodules.into_boxed_slice(), free_items: items.into_boxed_slice(), trait_items: trait_items.into_boxed_slice(), diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index cb4760c18de5f..bb36b2c936477 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -24,6 +24,9 @@ use crate::ty::{EarlyBinder, ImplSubject, TyCtxt}; /// bodies. The Ids are in visitor order. This is used to partition a pass between modules. #[derive(Debug, HashStable, Encodable, Decodable)] pub struct ModuleItems { + /// Whether this represents the whole crate, in which case we need to add `CRATE_OWNER_ID` to + /// the iterators if we want to account for the crate root. + add_root: bool, submodules: Box<[OwnerId]>, free_items: Box<[ItemId]>, trait_items: Box<[TraitItemId]>, @@ -60,9 +63,10 @@ impl ModuleItems { } pub fn owners(&self) -> impl Iterator { - self.free_items - .iter() - .map(|id| id.owner_id) + self.add_root + .then_some(CRATE_OWNER_ID) + .into_iter() + .chain(self.free_items.iter().map(|id| id.owner_id)) .chain(self.trait_items.iter().map(|id| id.owner_id)) .chain(self.impl_items.iter().map(|id| id.owner_id)) .chain(self.foreign_items.iter().map(|id| id.owner_id)) diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index b4d8b2e717639..7b1403c5e7ef6 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2118,7 +2118,7 @@ impl<'tcx> TyCtxt<'tcx> { ) -> &'tcx rustc_hir::def_path_hash_map::DefPathHashMap { // Create a dependency to the crate to be sure we re-execute this when the amount of // definitions change. - self.ensure_ok().hir_crate(()); + self.ensure_ok().hir_crate_items(()); // Freeze definitions once we start iterating on them, to prevent adding new ones // while iterating. If some query needs to add definitions, it should be `ensure`d above. self.untracked.definitions.freeze().def_path_hash_to_def_index_map() diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 204f8decffcc0..d73600a463631 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -387,8 +387,6 @@ pub(crate) fn run_global_ctxt( ctxt.external_traits.insert(sized_trait_did, sized_trait); } - debug!("crate: {:?}", tcx.hir_crate(())); - let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt)); if krate.module.doc_value().is_empty() { diff --git a/tests/ui/consts/const-unstable-intrinsic.stderr b/tests/ui/consts/const-unstable-intrinsic.stderr index 973c7bae58691..835113ccda51f 100644 --- a/tests/ui/consts/const-unstable-intrinsic.stderr +++ b/tests/ui/consts/const-unstable-intrinsic.stderr @@ -24,10 +24,7 @@ error: `size_of_val` is not yet stable as a const intrinsic LL | unstable_intrinsic::size_of_val(&x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: add `#![feature(unstable)]` to the crate attributes to enable - | -LL + #![feature(unstable)] - | + = help: add `#![feature(unstable)]` to the crate attributes to enable error: `align_of_val` is not yet stable as a const intrinsic --> $DIR/const-unstable-intrinsic.rs:20:9 @@ -35,10 +32,7 @@ error: `align_of_val` is not yet stable as a const intrinsic LL | unstable_intrinsic::align_of_val(&x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: add `#![feature(unstable)]` to the crate attributes to enable - | -LL + #![feature(unstable)] - | + = help: add `#![feature(unstable)]` to the crate attributes to enable error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]` --> $DIR/const-unstable-intrinsic.rs:24:9 diff --git a/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr b/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr index 0f2006e932b34..0a5f58288fa33 100644 --- a/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr +++ b/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr @@ -4,10 +4,7 @@ error: `foobar` is not yet stable as a const fn LL | foobar(); | ^^^^^^^^ | -help: add `#![feature(const_foobar)]` to the crate attributes to enable - | -LL + #![feature(const_foobar)] - | + = help: add `#![feature(const_foobar)]` to the crate attributes to enable error: aborting due to 1 previous error diff --git a/tests/ui/traits/const-traits/staged-api-user-crate.stderr b/tests/ui/traits/const-traits/staged-api-user-crate.stderr index 8ac83770cf7a4..1365be7e3890b 100644 --- a/tests/ui/traits/const-traits/staged-api-user-crate.stderr +++ b/tests/ui/traits/const-traits/staged-api-user-crate.stderr @@ -15,10 +15,7 @@ error: `staged_api::MyTrait` is not yet stable as a const trait LL | Unstable::func(); | ^^^^^^^^^^^^^^^^ | -help: add `#![feature(unstable)]` to the crate attributes to enable - | -LL + #![feature(unstable)] - | + = help: add `#![feature(unstable)]` to the crate attributes to enable error: aborting due to 2 previous errors From 7fa94af5565918146af0b86d3857321b097e956f Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Wed, 18 Jun 2025 16:52:38 +0000 Subject: [PATCH 04/14] Make feature suggestion more consistent. --- .../src/check_consts/check.rs | 8 ----- .../rustc_const_eval/src/check_consts/ops.rs | 22 ++----------- compiler/rustc_const_eval/src/errors.rs | 4 +-- compiler/rustc_hir_analysis/src/check/mod.rs | 4 +-- .../rustc_hir_analysis/src/check/wfcheck.rs | 2 +- compiler/rustc_hir_typeck/src/errors.rs | 6 ---- compiler/rustc_hir_typeck/src/expr.rs | 21 +++--------- compiler/rustc_hir_typeck/src/method/probe.rs | 1 - compiler/rustc_middle/src/ty/context.rs | 33 +++++++------------ .../src/error_reporting/traits/suggestions.rs | 6 +--- .../ui/consts/const-unstable-intrinsic.stderr | 10 ++++-- ...bility-attribute-implies-no-feature.stderr | 5 ++- .../const-traits/staged-api-user-crate.stderr | 5 ++- 13 files changed, 40 insertions(+), 87 deletions(-) diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 4f252f3ccd489..576b174369d46 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -463,12 +463,6 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { ); } - fn crate_inject_span(&self) -> Option { - self.tcx.hir_crate_items(()).definitions().next().and_then(|id| { - self.tcx.crate_level_attribute_injection_span(self.tcx.local_def_id_to_hir_id(id)) - }) - } - /// Check the const stability of the given item (fn or trait). fn check_callee_stability(&mut self, def_id: DefId) { match self.tcx.lookup_const_stability(def_id) { @@ -543,7 +537,6 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { feature, feature_enabled, safe_to_expose_on_stable: callee_safe_to_expose_on_stable, - suggestion_span: self.crate_inject_span(), is_function_call: self.tcx.def_kind(def_id) != DefKind::Trait, }); } @@ -919,7 +912,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { name: intrinsic.name, feature, const_stable_indirect: is_const_stable, - suggestion: self.crate_inject_span(), }); } Some(attrs::ConstStability { diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index 9c30dbff99eb3..887275e7294d2 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -1,8 +1,8 @@ //! Concrete error types for all operations which may be invalid in a certain const context. use hir::{ConstContext, LangItem}; +use rustc_errors::Diag; use rustc_errors::codes::*; -use rustc_errors::{Applicability, Diag}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_infer::infer::TyCtxtInferExt; @@ -384,7 +384,6 @@ pub(crate) struct CallUnstable { /// expose on stable. pub feature_enabled: bool, pub safe_to_expose_on_stable: bool, - pub suggestion_span: Option, /// true if `def_id` is the function we are calling, false if `def_id` is an unstable trait. pub is_function_call: bool, } @@ -412,20 +411,7 @@ impl<'tcx> NonConstOp<'tcx> for CallUnstable { def_path: ccx.tcx.def_path_str(self.def_id), }) }; - // FIXME: make this translatable - let msg = format!("add `#![feature({})]` to the crate attributes to enable", self.feature); - #[allow(rustc::untranslatable_diagnostic)] - if let Some(span) = self.suggestion_span { - err.span_suggestion_verbose( - span, - msg, - format!("#![feature({})]\n", self.feature), - Applicability::MachineApplicable, - ); - } else { - err.help(msg); - } - + ccx.tcx.disabled_nightly_features(&mut err, [(String::new(), self.feature)]); err } } @@ -452,7 +438,6 @@ pub(crate) struct IntrinsicUnstable { pub name: Symbol, pub feature: Symbol, pub const_stable_indirect: bool, - pub suggestion: Option, } impl<'tcx> NonConstOp<'tcx> for IntrinsicUnstable { @@ -472,8 +457,7 @@ impl<'tcx> NonConstOp<'tcx> for IntrinsicUnstable { span, name: self.name, feature: self.feature, - suggestion: self.suggestion, - help: self.suggestion.is_none(), + suggestion: ccx.tcx.crate_level_attribute_injection_span(), }) } } diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 037cbf777e70b..69c71aef9f337 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -136,9 +136,7 @@ pub(crate) struct UnstableIntrinsic { code = "#![feature({feature})]\n", applicability = "machine-applicable" )] - pub suggestion: Option, - #[help(const_eval_unstable_intrinsic_suggestion)] - pub help: bool, + pub suggestion: Span, } #[derive(Diagnostic)] diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index c5c7e6b2aa722..ed43a0b21e406 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -302,9 +302,7 @@ fn default_body_is_unstable( reason: reason_str, }); - let inject_span = item_did - .as_local() - .and_then(|id| tcx.crate_level_attribute_injection_span(tcx.local_def_id_to_hir_id(id))); + let inject_span = item_did.is_local().then(|| tcx.crate_level_attribute_injection_span()); rustc_session::parse::add_feature_diagnostics_for_issue( &mut err, &tcx.sess, diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index b8dc01cbc03cf..8198c24a7f003 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -1064,7 +1064,7 @@ fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) -> Result<(), Ok(..) => Some(vec![(adt_const_params_feature_string, sym::adt_const_params)]), }; if let Some(features) = may_suggest_feature { - tcx.disabled_nightly_features(&mut diag, Some(param.hir_id), features); + tcx.disabled_nightly_features(&mut diag, features); } Err(diag.emit()) diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index abb8cdc1cdf3c..5f59b3ad96e04 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -30,8 +30,6 @@ pub(crate) struct BaseExpressionDoubleDot { )] pub default_field_values_suggestion: Option, #[subdiagnostic] - pub default_field_values_help: Option, - #[subdiagnostic] pub add_expr: Option, #[subdiagnostic] pub remove_dots: Option, @@ -61,10 +59,6 @@ pub(crate) struct BaseExpressionDoubleDotAddExpr { pub span: Span, } -#[derive(Subdiagnostic)] -#[help(hir_typeck_base_expression_double_dot_enable_default_field_values)] -pub(crate) struct BaseExpressionDoubleDotEnableDefaultFieldValues; - #[derive(Diagnostic)] #[diag(hir_typeck_field_multiply_specified_in_initializer, code = E0062)] pub(crate) struct FieldMultiplySpecifiedInInitializer { diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 30bf557dc93a8..26370784f56ce 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -42,10 +42,9 @@ use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectatio use crate::coercion::{CoerceMany, DynamicCoerceMany}; use crate::errors::{ AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr, - BaseExpressionDoubleDotEnableDefaultFieldValues, BaseExpressionDoubleDotRemove, - CantDereference, FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct, - HelpUseLatestEdition, NakedAsmOutsideNakedFn, NoFieldOnType, NoFieldOnVariant, - ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive, + BaseExpressionDoubleDotRemove, CantDereference, FieldMultiplySpecifiedInInitializer, + FunctionalRecordUpdateOnNonStruct, HelpUseLatestEdition, NakedAsmOutsideNakedFn, NoFieldOnType, + NoFieldOnVariant, ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive, TypeMismatchFruTypo, YieldExprOutsideOfCoroutine, }; use crate::{ @@ -2133,7 +2132,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } if !self.tcx.features().default_field_values() { - let sugg = self.tcx.crate_level_attribute_injection_span(expr.hir_id); + let sugg = self.tcx.crate_level_attribute_injection_span(); self.dcx().emit_err(BaseExpressionDoubleDot { span: span.shrink_to_hi(), // We only mention enabling the feature if this is a nightly rustc *and* the @@ -2141,18 +2140,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { default_field_values_suggestion: if self.tcx.sess.is_nightly_build() && missing_mandatory_fields.is_empty() && !missing_optional_fields.is_empty() - && sugg.is_some() { - sugg - } else { - None - }, - default_field_values_help: if self.tcx.sess.is_nightly_build() - && missing_mandatory_fields.is_empty() - && !missing_optional_fields.is_empty() - && sugg.is_none() - { - Some(BaseExpressionDoubleDotEnableDefaultFieldValues) + Some(sugg) } else { None }, diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index a3fdf200c8ea0..589dbb531168b 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -1727,7 +1727,6 @@ impl<'tcx> Pick<'tcx> { } tcx.disabled_nightly_features( lint, - Some(scope_expr_id), self.unstable_candidates.iter().map(|(candidate, feature)| { (format!(" `{}`", tcx.def_path_str(candidate.item.def_id)), *feature) }), diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 7b1403c5e7ef6..23056820bc67e 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -3165,42 +3165,33 @@ impl<'tcx> TyCtxt<'tcx> { lint_level(self.sess, lint, level, Some(span.into()), decorate); } - /// Find the crate root and the appropriate span where `use` and outer attributes can be - /// inserted at. - pub fn crate_level_attribute_injection_span(self, hir_id: HirId) -> Option { - for (_hir_id, node) in self.hir_parent_iter(hir_id) { - if let hir::Node::Crate(m) = node { - return Some(m.spans.inject_use_span.shrink_to_lo()); - } - } - None + /// Find the appropriate span where `use` and outer attributes can be inserted at. + pub fn crate_level_attribute_injection_span(self) -> Span { + let node = self.hir_node(hir::CRATE_HIR_ID); + let hir::Node::Crate(m) = node else { bug!() }; + m.spans.inject_use_span.shrink_to_lo() } pub fn disabled_nightly_features( self, diag: &mut Diag<'_, E>, - hir_id: Option, features: impl IntoIterator, ) { if !self.sess.is_nightly_build() { return; } - let span = hir_id.and_then(|id| self.crate_level_attribute_injection_span(id)); + let span = self.crate_level_attribute_injection_span(); for (desc, feature) in features { // FIXME: make this string translatable let msg = format!("add `#![feature({feature})]` to the crate attributes to enable{desc}"); - if let Some(span) = span { - diag.span_suggestion_verbose( - span, - msg, - format!("#![feature({feature})]\n"), - Applicability::MaybeIncorrect, - ); - } else { - diag.help(msg); - } + diag.span_suggestion_verbose( + span, + msg, + format!("#![feature({feature})]\n"), + Applicability::MaybeIncorrect, + ); } } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index ee5a5b247ce8c..6d07ae021ae9d 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3575,11 +3575,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } ObligationCauseCode::TrivialBound => { err.help("see issue #48214"); - tcx.disabled_nightly_features( - err, - Some(tcx.local_def_id_to_hir_id(body_id)), - [(String::new(), sym::trivial_bounds)], - ); + tcx.disabled_nightly_features(err, [(String::new(), sym::trivial_bounds)]); } ObligationCauseCode::OpaqueReturnType(expr_info) => { let (expr_ty, expr) = if let Some((expr_ty, hir_id)) = expr_info { diff --git a/tests/ui/consts/const-unstable-intrinsic.stderr b/tests/ui/consts/const-unstable-intrinsic.stderr index 835113ccda51f..973c7bae58691 100644 --- a/tests/ui/consts/const-unstable-intrinsic.stderr +++ b/tests/ui/consts/const-unstable-intrinsic.stderr @@ -24,7 +24,10 @@ error: `size_of_val` is not yet stable as a const intrinsic LL | unstable_intrinsic::size_of_val(&x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: add `#![feature(unstable)]` to the crate attributes to enable +help: add `#![feature(unstable)]` to the crate attributes to enable + | +LL + #![feature(unstable)] + | error: `align_of_val` is not yet stable as a const intrinsic --> $DIR/const-unstable-intrinsic.rs:20:9 @@ -32,7 +35,10 @@ error: `align_of_val` is not yet stable as a const intrinsic LL | unstable_intrinsic::align_of_val(&x); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: add `#![feature(unstable)]` to the crate attributes to enable +help: add `#![feature(unstable)]` to the crate attributes to enable + | +LL + #![feature(unstable)] + | error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]` --> $DIR/const-unstable-intrinsic.rs:24:9 diff --git a/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr b/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr index 0a5f58288fa33..0f2006e932b34 100644 --- a/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr +++ b/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr @@ -4,7 +4,10 @@ error: `foobar` is not yet stable as a const fn LL | foobar(); | ^^^^^^^^ | - = help: add `#![feature(const_foobar)]` to the crate attributes to enable +help: add `#![feature(const_foobar)]` to the crate attributes to enable + | +LL + #![feature(const_foobar)] + | error: aborting due to 1 previous error diff --git a/tests/ui/traits/const-traits/staged-api-user-crate.stderr b/tests/ui/traits/const-traits/staged-api-user-crate.stderr index 1365be7e3890b..8ac83770cf7a4 100644 --- a/tests/ui/traits/const-traits/staged-api-user-crate.stderr +++ b/tests/ui/traits/const-traits/staged-api-user-crate.stderr @@ -15,7 +15,10 @@ error: `staged_api::MyTrait` is not yet stable as a const trait LL | Unstable::func(); | ^^^^^^^^^^^^^^^^ | - = help: add `#![feature(unstable)]` to the crate attributes to enable +help: add `#![feature(unstable)]` to the crate attributes to enable + | +LL + #![feature(unstable)] + | error: aborting due to 2 previous errors From 0a4507cf64ef26463e24b41d14c576cd31f2a8ce Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sat, 14 Jun 2025 22:01:12 +1000 Subject: [PATCH 05/14] rustdoc_json: Add static asserts for the size of important types. A lot of these are large! Lots of room for improvement in the future. --- src/librustdoc/json/mod.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 2feadce26d09f..8cf3432d3bf12 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -382,3 +382,33 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { &self.cache } } + +// Some nodes are used a lot. Make sure they don't unintentionally get bigger. +// +// These assertions are here, not in `src/rustdoc-json-types/lib.rs` where the types are defined, +// because we have access to `static_assert_size` here. +#[cfg(target_pointer_width = "64")] +mod size_asserts { + use rustc_data_structures::static_assert_size; + + use super::types::*; + // tidy-alphabetical-start + static_assert_size!(AssocItemConstraint, 208); + static_assert_size!(Crate, 184); + static_assert_size!(ExternalCrate, 48); + static_assert_size!(FunctionPointer, 168); + static_assert_size!(GenericArg, 80); + static_assert_size!(GenericArgs, 104); + static_assert_size!(GenericBound, 72); + static_assert_size!(GenericParamDef, 136); + static_assert_size!(Impl, 304); + // `Item` contains a `PathBuf`, which is different sizes on different OSes. + static_assert_size!(Item, 528 + size_of::()); + static_assert_size!(ItemSummary, 32); + static_assert_size!(PolyTrait, 64); + static_assert_size!(PreciseCapturingArg, 32); + static_assert_size!(TargetFeature, 80); + static_assert_size!(Type, 80); + static_assert_size!(WherePredicate, 160); + // tidy-alphabetical-end +} From 82f8220922599ae6123d42b6db2a634af282238e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 16 Jun 2025 09:52:23 +1000 Subject: [PATCH 06/14] rustdoc_json: Add a test for some `GenericArgs` cases. --- tests/rustdoc-json/generic-args.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/rustdoc-json/generic-args.rs diff --git a/tests/rustdoc-json/generic-args.rs b/tests/rustdoc-json/generic-args.rs new file mode 100644 index 0000000000000..e48c3329f56ba --- /dev/null +++ b/tests/rustdoc-json/generic-args.rs @@ -0,0 +1,20 @@ +pub struct MyStruct(u32); + +pub trait MyTrait { + type MyType; + fn my_fn(&self); +} + +impl MyTrait for MyStruct { + type MyType = u32; + fn my_fn(&self) {} +} + +//@ is "$.index[?(@.name=='my_fn1')].inner.function.sig.inputs[0][1].qualified_path.args" {\"angle_bracketed\":{\"args\":[],\"constraints\":[]}} +//@ is "$.index[?(@.name=='my_fn1')].inner.function.sig.inputs[0][1].qualified_path.self_type.resolved_path.args" {\"angle_bracketed\":{\"args\":[],\"constraints\":[]}} +pub fn my_fn1(_: ::MyType) {} + +//@ is "$.index[?(@.name=='my_fn2')].inner.function.sig.inputs[0][1].dyn_trait.traits[0].trait.args.angle_bracketed.constraints[0].args" {\"angle_bracketed\":{\"args\":[],\"constraints\":[]}} +pub fn my_fn2(_: IntoIterator) {} + +fn main() {} From 74371cab27add086cd2a3b895c5501205fadae02 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sat, 14 Jun 2025 20:14:51 +1000 Subject: [PATCH 07/14] rustdoc_json: Fix handling of paths with no generic args. A path without generic args, like `Reader`, currently has JSON produced like this: ``` {"path":"Reader","id":286,"args":{"angle_bracketed":{"args":[],"constraints":[]}}} ``` Even though `types::Path::args` is `Option` and allows for "no args", instead it gets represented as "empty args". (More like `Reader<>` than `Reader`.) This is due to a problem in `clean::Path::from_clean`. It only produces `None` if the path is an empty string. This commit changes it to also produce `None` if there are no generic args. The example above becomes: ``` {"path":"Reader","id":286,"args":null} ``` I looked at a few examples and saw this reduce the size of the JSON output by 3-9%. The commit also adds an assertion that non-final segments don't have any generics; something the old code was implicitly relying on. Note: the original sin here is that `clean::PathSegment::args` is not an `Option`, unlike `{ast,hir}::PathSegment::args`. I want to fix that, but it can be done separately. --- src/librustdoc/json/conversions.rs | 19 ++++++++++++++++++- tests/rustdoc-json/generic-args.rs | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 6bdf3b5fe3876..3b270d111a17e 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -580,7 +580,24 @@ impl FromClean for 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.args.into_json(renderer))), + args: { + if let Some((final_seg, rest_segs)) = path.segments.split_last() { + // In general, `clean::Path` can hold things like + // `std::vec::Vec::::new`, where generic args appear + // in a middle segment. But for the places where `Path` is + // used by rustdoc-json-types, generic args can only be + // used in the final segment, e.g. `std::vec::Vec`. So + // check that the non-final segments have no generic args. + assert!(rest_segs.iter().all(|seg| seg.args.is_empty())); + if final_seg.args.is_empty() { + None + } else { + Some(Box::new(final_seg.args.into_json(renderer))) + } + } else { + None // no generics on any segments because there are no segments + } + }, } } } diff --git a/tests/rustdoc-json/generic-args.rs b/tests/rustdoc-json/generic-args.rs index e48c3329f56ba..e87c1e6230432 100644 --- a/tests/rustdoc-json/generic-args.rs +++ b/tests/rustdoc-json/generic-args.rs @@ -11,7 +11,7 @@ impl MyTrait for MyStruct { } //@ is "$.index[?(@.name=='my_fn1')].inner.function.sig.inputs[0][1].qualified_path.args" {\"angle_bracketed\":{\"args\":[],\"constraints\":[]}} -//@ is "$.index[?(@.name=='my_fn1')].inner.function.sig.inputs[0][1].qualified_path.self_type.resolved_path.args" {\"angle_bracketed\":{\"args\":[],\"constraints\":[]}} +//@ is "$.index[?(@.name=='my_fn1')].inner.function.sig.inputs[0][1].qualified_path.self_type.resolved_path.args" null pub fn my_fn1(_: ::MyType) {} //@ is "$.index[?(@.name=='my_fn2')].inner.function.sig.inputs[0][1].dyn_trait.traits[0].trait.args.angle_bracketed.constraints[0].args" {\"angle_bracketed\":{\"args\":[],\"constraints\":[]}} From dba34b324d617f10a91ce3070ea2c1d04cdd4fa9 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sat, 14 Jun 2025 20:22:30 +1000 Subject: [PATCH 08/14] Fix some comments. As per the previous commit, generic args here can only appear on the final segment. So make the comments obey that constraint. --- src/rustdoc-json-types/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 1f93895ae0762..f9f4016eb6651 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -277,8 +277,8 @@ pub struct PolyTrait { /// A set of generic arguments provided to a path segment, e.g. /// /// ```text -/// std::option::Option::::None -/// ^^^^^ +/// std::option::Option +/// ^^^^^ /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -331,7 +331,7 @@ pub enum GenericArg { Const(Constant), /// A generic argument that's explicitly set to be inferred. /// ```text - /// std::vec::Vec::<_>::new() + /// std::vec::Vec::<_> /// ^ /// ``` Infer, From 91818bab01b22001e026686009e5b839fc49e0c4 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Sat, 14 Jun 2025 21:39:09 +1000 Subject: [PATCH 09/14] rustdoc_json: represent generic args consistently. They show up in three places: once as `Option>`, once as `Box`, and once as `GenericArgs`. The first option is best. It is more compact because generic args are often missing. This commit changes the latter two to the former. Example output, before and after, for the `AssocItemConstraint` change: ``` {"name":"Offset","args":{"angle_bracketed":{"args":[],"constraints":[]}},"binding":{...}} {"name":"Offset","args":null,"binding":{...}} ``` Example output, before and after, for the `Type::QualifiedPath` change: ``` {"qualified_path":{"name":"Offset","args":{"angle_bracketed":{"args":[],"constraints":[]}}, ...}} {"qualified_path":{"name":"Offset","args":null, ...}} ``` This reduces JSON output size, but not by much (e.g. 0.5%), because `AssocItemConstraint` and `Type::QualifiedPath` are uncommon. --- src/librustdoc/json/conversions.rs | 17 ++++++++--------- src/librustdoc/json/mod.rs | 2 +- src/rustdoc-json-types/lib.rs | 8 ++++---- src/tools/jsondoclint/src/validator.rs | 13 ++++++------- tests/rustdoc-json/generic-args.rs | 4 ++-- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 3b270d111a17e..b9df594dcca96 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -170,10 +170,13 @@ pub(crate) fn from_deprecation(deprecation: attrs::Deprecation) -> Deprecation { Deprecation { since, note: note.map(|s| s.to_string()) } } -impl FromClean for GenericArgs { +impl FromClean for Option> { fn from_clean(args: &clean::GenericArgs, renderer: &JsonRenderer<'_>) -> Self { use clean::GenericArgs::*; - match args { + if args.is_empty() { + return None; + } + Some(Box::new(match args { AngleBracketed { args, constraints } => GenericArgs::AngleBracketed { args: args.into_json(renderer), constraints: constraints.into_json(renderer), @@ -183,7 +186,7 @@ impl FromClean for GenericArgs { output: output.as_ref().map(|a| a.as_ref().into_json(renderer)), }, ReturnTypeNotation => GenericArgs::ReturnTypeNotation, - } + })) } } @@ -589,11 +592,7 @@ impl FromClean for Path { // used in the final segment, e.g. `std::vec::Vec`. So // check that the non-final segments have no generic args. assert!(rest_segs.iter().all(|seg| seg.args.is_empty())); - if final_seg.args.is_empty() { - None - } else { - Some(Box::new(final_seg.args.into_json(renderer))) - } + final_seg.args.into_json(renderer) } else { None // no generics on any segments because there are no segments } @@ -608,7 +607,7 @@ impl FromClean for Type { Self::QualifiedPath { name: assoc.name.to_string(), - args: Box::new(assoc.args.into_json(renderer)), + args: assoc.args.into_json(renderer), self_type: Box::new(self_type.into_json(renderer)), trait_: trait_.as_ref().map(|trait_| trait_.into_json(renderer)), } diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 8cf3432d3bf12..3aceffa3fdc96 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -393,7 +393,7 @@ mod size_asserts { use super::types::*; // tidy-alphabetical-start - static_assert_size!(AssocItemConstraint, 208); + static_assert_size!(AssocItemConstraint, 112); static_assert_size!(Crate, 184); static_assert_size!(ExternalCrate, 48); static_assert_size!(FunctionPointer, 168); diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index f9f4016eb6651..44bb14eeaf78f 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -37,8 +37,8 @@ pub type FxHashMap = HashMap; // re-export for use in src/librustdoc // will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line // are deliberately not in a doc comment, because they need not be in public docs.) // -// Latest feature: Pretty printing of inline attributes changed -pub const FORMAT_VERSION: u32 = 48; +// Latest feature: improve handling of generic args +pub const FORMAT_VERSION: u32 = 49; /// The root of the emitted JSON blob. /// @@ -362,7 +362,7 @@ pub struct AssocItemConstraint { /// The name of the associated type/constant. pub name: String, /// Arguments provided to the associated type/constant. - pub args: GenericArgs, + pub args: Option>, /// The kind of bound applied to the associated type/constant. pub binding: AssocItemConstraintKind, } @@ -1118,7 +1118,7 @@ pub enum Type { /// as BetterIterator>::Item<'static> /// // ^^^^^^^^^ /// ``` - args: Box, + args: Option>, /// The type with which this type is associated. /// /// ```ignore (incomplete expression) diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs index 8c9e4c8bb3a60..0a4051fcbe8cd 100644 --- a/src/tools/jsondoclint/src/validator.rs +++ b/src/tools/jsondoclint/src/validator.rs @@ -271,7 +271,7 @@ impl<'a> Validator<'a> { Type::RawPointer { is_mutable: _, type_ } => self.check_type(&**type_), Type::BorrowedRef { lifetime: _, is_mutable: _, type_ } => self.check_type(&**type_), Type::QualifiedPath { name: _, args, self_type, trait_ } => { - self.check_generic_args(&**args); + self.check_opt_generic_args(&args); self.check_type(&**self_type); if let Some(trait_) = trait_ { self.check_path(trait_, PathKind::Trait); @@ -309,13 +309,12 @@ impl<'a> Validator<'a> { self.fail(&x.id, ErrorKind::Custom(format!("No entry in '$.paths' for {x:?}"))); } - if let Some(args) = &x.args { - self.check_generic_args(&**args); - } + self.check_opt_generic_args(&x.args); } - fn check_generic_args(&mut self, x: &'a GenericArgs) { - match x { + fn check_opt_generic_args(&mut self, x: &'a Option>) { + let Some(x) = x else { return }; + match &**x { GenericArgs::AngleBracketed { args, constraints } => { args.iter().for_each(|arg| self.check_generic_arg(arg)); constraints.iter().for_each(|bind| self.check_assoc_item_constraint(bind)); @@ -355,7 +354,7 @@ impl<'a> Validator<'a> { } fn check_assoc_item_constraint(&mut self, bind: &'a AssocItemConstraint) { - self.check_generic_args(&bind.args); + self.check_opt_generic_args(&bind.args); match &bind.binding { AssocItemConstraintKind::Equality(term) => self.check_term(term), AssocItemConstraintKind::Constraint(bounds) => { diff --git a/tests/rustdoc-json/generic-args.rs b/tests/rustdoc-json/generic-args.rs index e87c1e6230432..0f588820da75d 100644 --- a/tests/rustdoc-json/generic-args.rs +++ b/tests/rustdoc-json/generic-args.rs @@ -10,11 +10,11 @@ impl MyTrait for MyStruct { fn my_fn(&self) {} } -//@ is "$.index[?(@.name=='my_fn1')].inner.function.sig.inputs[0][1].qualified_path.args" {\"angle_bracketed\":{\"args\":[],\"constraints\":[]}} +//@ is "$.index[?(@.name=='my_fn1')].inner.function.sig.inputs[0][1].qualified_path.args" null //@ is "$.index[?(@.name=='my_fn1')].inner.function.sig.inputs[0][1].qualified_path.self_type.resolved_path.args" null pub fn my_fn1(_: ::MyType) {} -//@ is "$.index[?(@.name=='my_fn2')].inner.function.sig.inputs[0][1].dyn_trait.traits[0].trait.args.angle_bracketed.constraints[0].args" {\"angle_bracketed\":{\"args\":[],\"constraints\":[]}} +//@ is "$.index[?(@.name=='my_fn2')].inner.function.sig.inputs[0][1].dyn_trait.traits[0].trait.args.angle_bracketed.constraints[0].args" null pub fn my_fn2(_: IntoIterator) {} fn main() {} From 8c281613739436582f979d228be4cc1d4f7afa3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 17 Jun 2025 16:09:30 +0200 Subject: [PATCH 10/14] Add `get_host_target` function --- src/bootstrap/src/core/config/config.rs | 4 ++-- src/bootstrap/src/utils/helpers.rs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index ff0fda2d2e698..d3393afcae05a 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -49,7 +49,7 @@ use crate::core::download::is_download_ci_available; use crate::utils::channel; use crate::utils::exec::command; use crate::utils::execution_context::ExecutionContext; -use crate::utils::helpers::exe; +use crate::utils::helpers::{exe, get_host_target}; use crate::{GitInfo, OnceLock, TargetSelection, check_ci_llvm, helpers, t}; /// Each path in this list is considered "allowed" in the `download-rustc="if-unchanged"` logic. @@ -349,7 +349,7 @@ impl Config { stderr_is_tty: std::io::stderr().is_terminal(), // set by build.rs - host_target: TargetSelection::from_user(env!("BUILD_TRIPLE")), + host_target: get_host_target(), src: { let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index 2f18fb6031829..3c5f612daa7d4 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -178,6 +178,11 @@ pub fn symlink_dir(config: &Config, original: &Path, link: &Path) -> io::Result< } } +/// Return the host target on which we are currently running. +pub fn get_host_target() -> TargetSelection { + TargetSelection::from_user(env!("BUILD_TRIPLE")) +} + /// Rename a file if from and to are in the same filesystem or /// copy and remove the file otherwise pub fn move_file, Q: AsRef>(from: P, to: Q) -> io::Result<()> { From 21d21d5f81d6d0ac7e58275e40e982550951ec86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 17 Jun 2025 16:16:46 +0200 Subject: [PATCH 11/14] Normalize host target in snapshot tests --- src/bootstrap/src/core/builder/tests.rs | 48 ++++++++++++++++--------- src/bootstrap/src/utils/tests/mod.rs | 43 ++++++++++++++++++++++ 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 6268a2b59d6cc..f45afe0c789c2 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -9,6 +9,7 @@ use crate::Flags; use crate::core::build_steps::doc::DocumentationFormat; use crate::core::config::Config; use crate::utils::cache::ExecutedStep; +use crate::utils::helpers::get_host_target; use crate::utils::tests::git::{GitCtx, git_test}; static TEST_TRIPLE_1: &str = "i686-unknown-haiku"; @@ -1236,24 +1237,38 @@ fn any_debug() { /// The staging tests use insta for snapshot testing. /// See bootstrap's README on how to bless the snapshots. mod staging { + use crate::Build; + use crate::core::builder::Builder; use crate::core::builder::tests::{ TEST_TRIPLE_1, configure, configure_with_args, render_steps, run_build, }; + use crate::utils::tests::ConfigBuilder; #[test] fn build_compiler_stage_1() { - let mut cache = run_build( - &["compiler".into()], - configure_with_args(&["build", "--stage", "1"], &[TEST_TRIPLE_1], &[TEST_TRIPLE_1]), - ); - let steps = cache.into_executed_steps(); - insta::assert_snapshot!(render_steps(&steps), @r" - [build] rustc 0 -> std 0 - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 0 -> rustc 1 + insta::assert_snapshot!( + ConfigBuilder::build() + .path("compiler") + .stage(1) + .get_steps(), @r" + [build] rustc 0 -> std 0 + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 0 -> rustc 1 "); } + + impl ConfigBuilder { + fn get_steps(self) -> String { + let config = self.create_config(); + + let kind = config.cmd.kind(); + let build = Build::new(config); + let builder = Builder::new(&build); + builder.run_step_descriptions(&Builder::get_step_descriptions(kind), &builder.paths); + render_steps(&builder.cache.into_executed_steps()) + } + } } /// Renders the executed bootstrap steps for usage in snapshot tests with insta. @@ -1275,18 +1290,17 @@ fn render_steps(steps: &[ExecutedStep]) -> String { } let stage = if let Some(stage) = metadata.stage { format!("{stage} ") } else { "".to_string() }; - write!(record, "{} {stage}<{}>", metadata.name, metadata.target); + write!(record, "{} {stage}<{}>", metadata.name, normalize_target(metadata.target)); Some(record) }) - .map(|line| { - line.replace(TEST_TRIPLE_1, "target1") - .replace(TEST_TRIPLE_2, "target2") - .replace(TEST_TRIPLE_3, "target3") - }) .collect::>() .join("\n") } +fn normalize_target(target: TargetSelection) -> String { + target.to_string().replace(&get_host_target().to_string(), "host") +} + fn render_compiler(compiler: Compiler) -> String { - format!("rustc {} <{}>", compiler.stage, compiler.host) + format!("rustc {} <{}>", compiler.stage, normalize_target(compiler.host)) } diff --git a/src/bootstrap/src/utils/tests/mod.rs b/src/bootstrap/src/utils/tests/mod.rs index 73c500f6e3691..ec6293f3c2c19 100644 --- a/src/bootstrap/src/utils/tests/mod.rs +++ b/src/bootstrap/src/utils/tests/mod.rs @@ -1,3 +1,46 @@ //! This module contains shared utilities for bootstrap tests. +use crate::core::builder::Builder; +use crate::core::config::DryRun; +use crate::{Build, Config, Flags}; + pub mod git; + +/// Used to configure an invocation of bootstrap. +/// Currently runs in the rustc checkout, long-term it should be switched +/// to run in a (cache-primed) temporary directory instead. +pub struct ConfigBuilder { + args: Vec, +} + +impl ConfigBuilder { + pub fn from_args(args: &[&str]) -> Self { + Self::new(args) + } + + pub fn build() -> Self { + Self::new(&["build"]) + } + + pub fn path(mut self, path: &str) -> Self { + self.args.push(path.to_string()); + self + } + + pub fn stage(mut self, stage: u32) -> Self { + self.args.push("--stage".to_string()); + self.args.push(stage.to_string()); + self + } + + fn new(args: &[&str]) -> Self { + Self { args: args.iter().copied().map(String::from).collect() } + } + + pub fn create_config(mut self) -> Config { + let mut config = Config::parse(Flags::parse(&self.args)); + // Run in dry-check, otherwise the test would be too slow + config.set_dry_run(DryRun::SelfCheck); + config + } +} From 718e475cb12094cb182a8271b1855d8f1663dc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 18 Jun 2025 13:14:16 +0200 Subject: [PATCH 12/14] Clarify arrow in snapshot tests --- src/bootstrap/src/core/builder/tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index f45afe0c789c2..1d1c315c1cfdb 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1274,6 +1274,10 @@ mod staging { /// Renders the executed bootstrap steps for usage in snapshot tests with insta. /// Only renders certain important steps. /// Each value in `steps` should be a tuple of (Step, step output). +/// +/// The arrow in the rendered output (`X -> Y`) means `X builds Y`. +/// This is similar to the output printed by bootstrap to stdout, but here it is +/// generated purely for the purpose of tests. fn render_steps(steps: &[ExecutedStep]) -> String { steps .iter() From 7760f8ec4297fc9a8bf319ffefff02ba9b17458c Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Thu, 19 Jun 2025 18:31:56 +0800 Subject: [PATCH 13/14] add comment to `src/bootstrap/build.rs` --- src/bootstrap/build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bootstrap/build.rs b/src/bootstrap/build.rs index e0e32d3135354..d9810e899a043 100644 --- a/src/bootstrap/build.rs +++ b/src/bootstrap/build.rs @@ -1,6 +1,7 @@ use std::env; fn main() { + // this is needed because `HOST` is only available to build scripts. let host = env::var("HOST").unwrap(); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rustc-env=BUILD_TRIPLE={host}"); From ede48910fdb8956f4a23a3bb29ded754206a3ef2 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Thu, 19 Jun 2025 13:18:33 +0200 Subject: [PATCH 14/14] Update compiler/rustc_interface/src/passes.rs Co-authored-by: bjorn3 <17426603+bjorn3@users.noreply.github.com> --- compiler/rustc_interface/src/passes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index d29656fedd5b4..21bb8b1527114 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1010,7 +1010,7 @@ fn run_required_analyses(tcx: TyCtxt<'_>) { // Prefetch this to prevent multiple threads from blocking on it later. // This is needed since the `hir_id_validator::check_crate` call above is not guaranteed - // to use `hir_crate`. + // to use `hir_crate_items`. tcx.ensure_done().hir_crate_items(()); let sess = tcx.sess;