From cf26d82dafc95d73484b5688ccfc4eec8a9d3e52 Mon Sep 17 00:00:00 2001 From: pudongair <744355276@qq.com> Date: Wed, 26 Mar 2025 15:28:51 +0800 Subject: [PATCH 01/27] chore: remove redundant words in comment Signed-off-by: pudongair <744355276@qq.com> --- compiler/rustc_error_codes/src/error_codes/E0207.md | 2 +- library/core/src/clone.rs | 2 +- library/core/src/macros/mod.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_error_codes/src/error_codes/E0207.md b/compiler/rustc_error_codes/src/error_codes/E0207.md index 5b35748f4723c..f80b0093ecc53 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0207.md +++ b/compiler/rustc_error_codes/src/error_codes/E0207.md @@ -195,7 +195,7 @@ impl<'a> Contains for Foo { Please note that unconstrained lifetime parameters are not supported if they are being used by an associated type. -In cases where the associated type's lifetime is meant to be tied to the the +In cases where the associated type's lifetime is meant to be tied to the self type, and none of the methods on the trait need ownership or different mutability, then an option is to implement the trait on a borrowed type: diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index e0ac0bfc5289f..647463098610d 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -427,7 +427,7 @@ pub unsafe trait CloneToUninit { /// read or dropped, because even if it was previously valid, it may have been partially /// overwritten. /// - /// The caller may wish to to take care to deallocate the allocation pointed to by `dest`, + /// The caller may wish to take care to deallocate the allocation pointed to by `dest`, /// if applicable, to avoid a memory leak (but this is not a requirement). /// /// Implementors should avoid leaking values by, upon unwinding, dropping all component values diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 5f200b31d1ae7..c06f1b56399d2 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -1743,8 +1743,8 @@ pub(crate) mod builtin { /* compiler built-in */ } - /// Provide a list of type aliases and other opaque-type-containing type definitions. - /// This list will be used in the body of the item it is applied to define opaque + /// Provide a list of type aliases and other opaque-type-containing type definitions + /// to an item with a body. This list will be used in that body to define opaque /// types' hidden types. /// Can only be applied to things that have bodies. #[unstable( From d81559a453d7c9ca0ff9dbd64cf1a9933a54aeab Mon Sep 17 00:00:00 2001 From: Tobias Decking Date: Mon, 31 Mar 2025 19:30:05 +0200 Subject: [PATCH 02/27] Refactor `diy_float` --- library/core/src/num/diy_float.rs | 54 ++++--------------- .../core/src/num/flt2dec/strategy/grisu.rs | 8 +-- 2 files changed, 15 insertions(+), 47 deletions(-) diff --git a/library/core/src/num/diy_float.rs b/library/core/src/num/diy_float.rs index ce7f6475d0599..e054e7f3f10a7 100644 --- a/library/core/src/num/diy_float.rs +++ b/library/core/src/num/diy_float.rs @@ -21,61 +21,29 @@ pub struct Fp { impl Fp { /// Returns a correctly rounded product of itself and `other`. - pub fn mul(&self, other: &Fp) -> Fp { - const MASK: u64 = 0xffffffff; - let a = self.f >> 32; - let b = self.f & MASK; - let c = other.f >> 32; - let d = other.f & MASK; - let ac = a * c; - let bc = b * c; - let ad = a * d; - let bd = b * d; - let tmp = (bd >> 32) + (ad & MASK) + (bc & MASK) + (1 << 31) /* round */; - let f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); + pub fn mul(self, other: Self) -> Self { + let (lo, hi) = self.f.widening_mul(other.f); + let f = hi + (lo >> 63) /* round */; let e = self.e + other.e + 64; - Fp { f, e } + Self { f, e } } /// Normalizes itself so that the resulting mantissa is at least `2^63`. - pub fn normalize(&self) -> Fp { - let mut f = self.f; - let mut e = self.e; - if f >> (64 - 32) == 0 { - f <<= 32; - e -= 32; - } - if f >> (64 - 16) == 0 { - f <<= 16; - e -= 16; - } - if f >> (64 - 8) == 0 { - f <<= 8; - e -= 8; - } - if f >> (64 - 4) == 0 { - f <<= 4; - e -= 4; - } - if f >> (64 - 2) == 0 { - f <<= 2; - e -= 2; - } - if f >> (64 - 1) == 0 { - f <<= 1; - e -= 1; - } + pub fn normalize(self) -> Self { + let lz = self.f.leading_zeros(); + let f = self.f << lz; + let e = self.e - lz as i16; debug_assert!(f >= (1 << 63)); - Fp { f, e } + Self { f, e } } /// Normalizes itself to have the shared exponent. /// It can only decrease the exponent (and thus increase the mantissa). - pub fn normalize_to(&self, e: i16) -> Fp { + pub fn normalize_to(self, e: i16) -> Self { let edelta = self.e - e; assert!(edelta >= 0); let edelta = edelta as usize; assert_eq!(self.f << edelta >> edelta, self.f); - Fp { f: self.f << edelta, e } + Self { f: self.f << edelta, e } } } diff --git a/library/core/src/num/flt2dec/strategy/grisu.rs b/library/core/src/num/flt2dec/strategy/grisu.rs index 2816de4c63339..d3bbb0934e0ff 100644 --- a/library/core/src/num/flt2dec/strategy/grisu.rs +++ b/library/core/src/num/flt2dec/strategy/grisu.rs @@ -196,9 +196,9 @@ pub fn format_shortest_opt<'a>( let (minusk, cached) = cached_power(ALPHA - plus.e - 64, GAMMA - plus.e - 64); // scale fps. this gives the maximal error of 1 ulp (proved from Theorem 5.1). - let plus = plus.mul(&cached); - let minus = minus.mul(&cached); - let v = v.mul(&cached); + let plus = plus.mul(cached); + let minus = minus.mul(cached); + let v = v.mul(cached); debug_assert_eq!(plus.e, minus.e); debug_assert_eq!(plus.e, v.e); @@ -480,7 +480,7 @@ pub fn format_exact_opt<'a>( // normalize and scale `v`. let v = Fp { f: d.mant, e: d.exp }.normalize(); let (minusk, cached) = cached_power(ALPHA - v.e - 64, GAMMA - v.e - 64); - let v = v.mul(&cached); + let v = v.mul(cached); // divide `v` into integral and fractional parts. let e = -v.e as usize; From 5a2070175158d460044a3b0c26ea958a4be86a15 Mon Sep 17 00:00:00 2001 From: Christopher Berner Date: Thu, 3 Apr 2025 17:25:46 -0700 Subject: [PATCH 03/27] Change signature of File::try_lock and File::try_lock_shared These methods now return Result<(), TryLockError> instead of Result to make their use less errorprone --- library/std/src/fs.rs | 66 +++++++++++++++++++++++---- library/std/src/fs/tests.rs | 32 ++++++++----- library/std/src/sys/fs/hermit.rs | 11 +++-- library/std/src/sys/fs/solid.rs | 11 +++-- library/std/src/sys/fs/uefi.rs | 5 +- library/std/src/sys/fs/unix.rs | 39 ++++++++++------ library/std/src/sys/fs/unsupported.rs | 5 +- library/std/src/sys/fs/wasi.rs | 11 +++-- library/std/src/sys/fs/windows.rs | 17 +++---- 9 files changed, 135 insertions(+), 62 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 801baf3d99072..27f60ffb00c86 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -116,6 +116,22 @@ pub struct File { inner: fs_imp::File, } +/// An enumeration of possible errors which can occur while trying to acquire a lock +/// from the [`try_lock`] method and [`try_lock_shared`] method on a [`File`]. +/// +/// [`try_lock`]: File::try_lock +/// [`try_lock_shared`]: File::try_lock_shared +#[unstable(feature = "file_lock", issue = "130994")] +pub enum TryLockError { + /// The lock could not be acquired due to an I/O error on the file. The standard library will + /// not return an [`ErrorKind::WouldBlock`] error inside [`TryLockError::Error`] + /// + /// [`ErrorKind::WouldBlock`]: io::ErrorKind::WouldBlock + Error(io::Error), + /// The lock could not be acquired at this time because it is held by another handle/process. + WouldBlock, +} + /// Metadata information about a file. /// /// This structure is returned from the [`metadata`] or @@ -352,6 +368,27 @@ pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result inner(path.as_ref(), contents.as_ref()) } +#[unstable(feature = "file_lock", issue = "130994")] +impl fmt::Debug for TryLockError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TryLockError::Error(err) => err.fmt(f), + TryLockError::WouldBlock => "WouldBlock".fmt(f), + } + } +} + +#[unstable(feature = "file_lock", issue = "130994")] +impl fmt::Display for TryLockError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TryLockError::Error(_) => "lock acquisition failed due to I/O error", + TryLockError::WouldBlock => "lock acquisition failed because the operation would block", + } + .fmt(f) + } +} + impl File { /// Attempts to open a file in read-only mode. /// @@ -734,8 +771,8 @@ impl File { /// Try to acquire an exclusive lock on the file. /// - /// Returns `Ok(false)` if a different lock is already held on this file (via another - /// handle/descriptor). + /// Returns `Err(TryLockError::WouldBlock)` if a different lock is already held on this file + /// (via another handle/descriptor). /// /// This acquires an exclusive lock; no other file handle to this file may acquire another lock. /// @@ -777,23 +814,27 @@ impl File { /// /// ```no_run /// #![feature(file_lock)] - /// use std::fs::File; + /// use std::fs::{File, TryLockError}; /// /// fn main() -> std::io::Result<()> { /// let f = File::create("foo.txt")?; - /// f.try_lock()?; + /// match f.try_lock() { + /// Ok(_) => (), + /// Err(TryLockError::WouldBlock) => (), // Lock not acquired + /// Err(TryLockError::Error(err)) => return Err(err), + /// } /// Ok(()) /// } /// ``` #[unstable(feature = "file_lock", issue = "130994")] - pub fn try_lock(&self) -> io::Result { + pub fn try_lock(&self) -> Result<(), TryLockError> { self.inner.try_lock() } /// Try to acquire a shared (non-exclusive) lock on the file. /// - /// Returns `Ok(false)` if an exclusive lock is already held on this file (via another - /// handle/descriptor). + /// Returns `Err(TryLockError::WouldBlock)` if a different lock is already held on this file + /// (via another handle/descriptor). /// /// This acquires a shared lock; more than one file handle may hold a shared lock, but none may /// hold an exclusive lock at the same time. @@ -834,16 +875,21 @@ impl File { /// /// ```no_run /// #![feature(file_lock)] - /// use std::fs::File; + /// use std::fs::{File, TryLockError}; /// /// fn main() -> std::io::Result<()> { /// let f = File::open("foo.txt")?; - /// f.try_lock_shared()?; + /// match f.try_lock_shared() { + /// Ok(_) => (), + /// Err(TryLockError::WouldBlock) => (), // Lock not acquired + /// Err(TryLockError::Error(err)) => return Err(err), + /// } + /// /// Ok(()) /// } /// ``` #[unstable(feature = "file_lock", issue = "130994")] - pub fn try_lock_shared(&self) -> io::Result { + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { self.inner.try_lock_shared() } diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 4712e58980cc6..9c7986bcc6997 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1,7 +1,7 @@ use rand::RngCore; use crate::char::MAX_LEN_UTF8; -use crate::fs::{self, File, FileTimes, OpenOptions}; +use crate::fs::{self, File, FileTimes, OpenOptions, TryLockError}; use crate::io::prelude::*; use crate::io::{BorrowedBuf, ErrorKind, SeekFrom}; use crate::mem::MaybeUninit; @@ -28,6 +28,16 @@ macro_rules! check { }; } +macro_rules! check_would_block { + ($e:expr) => { + match $e { + Ok(_) => panic!("{} acquired lock when it should have failed", stringify!($e)), + Err(TryLockError::WouldBlock) => (), + Err(e) => panic!("{} failed with: {e}", stringify!($e)), + } + }; +} + #[cfg(windows)] macro_rules! error { ($e:expr, $s:expr) => { @@ -223,8 +233,8 @@ fn file_lock_multiple_shared() { check!(f2.lock_shared()); check!(f1.unlock()); check!(f2.unlock()); - assert!(check!(f1.try_lock_shared())); - assert!(check!(f2.try_lock_shared())); + check!(f1.try_lock_shared()); + check!(f2.try_lock_shared()); } #[test] @@ -243,12 +253,12 @@ fn file_lock_blocking() { // Check that shared locks block exclusive locks check!(f1.lock_shared()); - assert!(!check!(f2.try_lock())); + check_would_block!(f2.try_lock()); check!(f1.unlock()); // Check that exclusive locks block shared locks check!(f1.lock()); - assert!(!check!(f2.try_lock_shared())); + check_would_block!(f2.try_lock_shared()); } #[test] @@ -267,9 +277,9 @@ fn file_lock_drop() { // Check that locks are released when the File is dropped check!(f1.lock_shared()); - assert!(!check!(f2.try_lock())); + check_would_block!(f2.try_lock()); drop(f1); - assert!(check!(f2.try_lock())); + check!(f2.try_lock()); } #[test] @@ -288,10 +298,10 @@ fn file_lock_dup() { // Check that locks are not dropped if the File has been cloned check!(f1.lock_shared()); - assert!(!check!(f2.try_lock())); + check_would_block!(f2.try_lock()); let cloned = check!(f1.try_clone()); drop(f1); - assert!(!check!(f2.try_lock())); + check_would_block!(f2.try_lock()); drop(cloned) } @@ -307,9 +317,9 @@ fn file_lock_double_unlock() { // Check that both are released by unlock() check!(f1.lock()); check!(f1.lock_shared()); - assert!(!check!(f2.try_lock())); + check_would_block!(f2.try_lock()); check!(f1.unlock()); - assert!(check!(f2.try_lock())); + check!(f2.try_lock()); } #[test] diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index f83a2f90ed22a..d5412b2dda532 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -1,4 +1,5 @@ use crate::ffi::{CStr, OsStr, OsString, c_char}; +use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, Error, ErrorKind, IoSlice, IoSliceMut, SeekFrom}; use crate::os::hermit::ffi::OsStringExt; use crate::os::hermit::hermit_abi::{ @@ -12,7 +13,7 @@ use crate::sys::common::small_c_string::run_path_with_cstr; pub use crate::sys::fs::common::{copy, exists}; use crate::sys::pal::fd::FileDesc; use crate::sys::time::SystemTime; -use crate::sys::{cvt, unsupported}; +use crate::sys::{cvt, unsupported, unsupported_err}; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::{fmt, mem}; @@ -366,12 +367,12 @@ impl File { unsupported() } - pub fn try_lock(&self) -> io::Result { - unsupported() + pub fn try_lock(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) } - pub fn try_lock_shared(&self) -> io::Result { - unsupported() + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) } pub fn unlock(&self) -> io::Result<()> { diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index 39de933b7248b..3bfb39bac95bc 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -2,6 +2,7 @@ use crate::ffi::{CStr, CString, OsStr, OsString}; use crate::fmt; +use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::mem::MaybeUninit; use crate::os::raw::{c_int, c_short}; @@ -11,7 +12,7 @@ use crate::sync::Arc; pub use crate::sys::fs::common::exists; use crate::sys::pal::{abi, error}; use crate::sys::time::SystemTime; -use crate::sys::unsupported; +use crate::sys::{unsupported, unsupported_err}; use crate::sys_common::ignore_notfound; type CIntNotMinusOne = core::num::niche_types::NotAllOnes; @@ -352,12 +353,12 @@ impl File { unsupported() } - pub fn try_lock(&self) -> io::Result { - unsupported() + pub fn try_lock(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) } - pub fn try_lock_shared(&self) -> io::Result { - unsupported() + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) } pub fn unlock(&self) -> io::Result<()> { diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index d6ae86bd3d26e..416c90b98b6d3 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -2,6 +2,7 @@ use r_efi::protocols::file; use crate::ffi::OsString; use crate::fmt; +use crate::fs::TryLockError; use crate::hash::Hash; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::path::{Path, PathBuf}; @@ -227,11 +228,11 @@ impl File { self.0 } - pub fn try_lock(&self) -> io::Result { + pub fn try_lock(&self) -> Result<(), TryLockError> { self.0 } - pub fn try_lock_shared(&self) -> io::Result { + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { self.0 } diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 87865be0387d5..9f27542849f6a 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -74,6 +74,7 @@ use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, st use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt::{self, Write as _}; +use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; use crate::os::unix::prelude::*; @@ -1307,15 +1308,17 @@ impl File { target_os = "netbsd", target_vendor = "apple", ))] - pub fn try_lock(&self) -> io::Result { + pub fn try_lock(&self) -> Result<(), TryLockError> { let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) }); - if let Err(ref err) = result { + if let Err(err) = result { if err.kind() == io::ErrorKind::WouldBlock { - return Ok(false); + Err(TryLockError::WouldBlock) + } else { + Err(TryLockError::Error(err)) } + } else { + Ok(()) } - result?; - return Ok(true); } #[cfg(not(any( @@ -1325,8 +1328,11 @@ impl File { target_os = "netbsd", target_vendor = "apple", )))] - pub fn try_lock(&self) -> io::Result { - Err(io::const_error!(io::ErrorKind::Unsupported, "try_lock() not supported")) + pub fn try_lock(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(io::const_error!( + io::ErrorKind::Unsupported, + "try_lock() not supported" + ))) } #[cfg(any( @@ -1336,15 +1342,17 @@ impl File { target_os = "netbsd", target_vendor = "apple", ))] - pub fn try_lock_shared(&self) -> io::Result { + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) }); - if let Err(ref err) = result { + if let Err(err) = result { if err.kind() == io::ErrorKind::WouldBlock { - return Ok(false); + Err(TryLockError::WouldBlock) + } else { + Err(TryLockError::Error(err)) } + } else { + Ok(()) } - result?; - return Ok(true); } #[cfg(not(any( @@ -1354,8 +1362,11 @@ impl File { target_os = "netbsd", target_vendor = "apple", )))] - pub fn try_lock_shared(&self) -> io::Result { - Err(io::const_error!(io::ErrorKind::Unsupported, "try_lock_shared() not supported")) + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(io::const_error!( + io::ErrorKind::Unsupported, + "try_lock_shared() not supported" + ))) } #[cfg(any( diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 45e93deffa3a4..0ff9533c04734 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -1,5 +1,6 @@ use crate::ffi::OsString; use crate::fmt; +use crate::fs::TryLockError; use crate::hash::{Hash, Hasher}; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::path::{Path, PathBuf}; @@ -206,11 +207,11 @@ impl File { self.0 } - pub fn try_lock(&self) -> io::Result { + pub fn try_lock(&self) -> Result<(), TryLockError> { self.0 } - pub fn try_lock_shared(&self) -> io::Result { + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { self.0 } diff --git a/library/std/src/sys/fs/wasi.rs b/library/std/src/sys/fs/wasi.rs index 773040571bc97..ebfc7377a2ead 100644 --- a/library/std/src/sys/fs/wasi.rs +++ b/library/std/src/sys/fs/wasi.rs @@ -1,4 +1,5 @@ use crate::ffi::{CStr, OsStr, OsString}; +use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::mem::{self, ManuallyDrop}; use crate::os::raw::c_int; @@ -10,7 +11,7 @@ use crate::sys::common::small_c_string::run_path_with_cstr; use crate::sys::fd::WasiFd; pub use crate::sys::fs::common::exists; use crate::sys::time::SystemTime; -use crate::sys::unsupported; +use crate::sys::{unsupported, unsupported_err}; use crate::sys_common::{AsInner, FromInner, IntoInner, ignore_notfound}; use crate::{fmt, iter, ptr}; @@ -461,12 +462,12 @@ impl File { unsupported() } - pub fn try_lock(&self) -> io::Result { - unsupported() + pub fn try_lock(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) } - pub fn try_lock_shared(&self) -> io::Result { - unsupported() + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { + Err(TryLockError::Error(unsupported_err())) } pub fn unlock(&self) -> io::Result<()> { diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 15727c996837b..6e54153325fba 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -3,6 +3,7 @@ use crate::alloc::{Layout, alloc, dealloc}; use crate::borrow::Cow; use crate::ffi::{OsStr, OsString, c_void}; +use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::mem::{self, MaybeUninit, offset_of}; use crate::os::windows::io::{AsHandle, BorrowedHandle}; @@ -397,7 +398,7 @@ impl File { self.acquire_lock(0) } - pub fn try_lock(&self) -> io::Result { + pub fn try_lock(&self) -> Result<(), TryLockError> { let result = cvt(unsafe { let mut overlapped = mem::zeroed(); c::LockFileEx( @@ -411,18 +412,18 @@ impl File { }); match result { - Ok(_) => Ok(true), + Ok(_) => Ok(()), Err(err) if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) || err.raw_os_error() == Some(c::ERROR_LOCK_VIOLATION as i32) => { - Ok(false) + Err(TryLockError::WouldBlock) } - Err(err) => Err(err), + Err(err) => Err(TryLockError::Error(err)), } } - pub fn try_lock_shared(&self) -> io::Result { + pub fn try_lock_shared(&self) -> Result<(), TryLockError> { let result = cvt(unsafe { let mut overlapped = mem::zeroed(); c::LockFileEx( @@ -436,14 +437,14 @@ impl File { }); match result { - Ok(_) => Ok(true), + Ok(_) => Ok(()), Err(err) if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) || err.raw_os_error() == Some(c::ERROR_LOCK_VIOLATION as i32) => { - Ok(false) + Err(TryLockError::WouldBlock) } - Err(err) => Err(err), + Err(err) => Err(TryLockError::Error(err)), } } From 7302f8eecd1bb3502c7189514ff00990af0988fc Mon Sep 17 00:00:00 2001 From: Christopher Berner Date: Sun, 6 Apr 2025 08:39:10 -0700 Subject: [PATCH 04/27] Implement error::Error for TryLockError --- library/std/src/fs.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 27f60ffb00c86..a1d4354d4fd40 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -21,7 +21,6 @@ mod tests; use crate::ffi::OsString; -use crate::fmt; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; use crate::path::{Path, PathBuf}; use crate::sealed::Sealed; @@ -29,6 +28,7 @@ use crate::sync::Arc; use crate::sys::fs as fs_imp; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::time::SystemTime; +use crate::{error, fmt}; /// An object providing access to an open file on the filesystem. /// @@ -368,6 +368,9 @@ pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result inner(path.as_ref(), contents.as_ref()) } +#[unstable(feature = "file_lock", issue = "130994")] +impl error::Error for TryLockError {} + #[unstable(feature = "file_lock", issue = "130994")] impl fmt::Debug for TryLockError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { From e5fb426a833e45565f13f81c2c664e77e0e351f1 Mon Sep 17 00:00:00 2001 From: Diego Ongaro Date: Sun, 13 Apr 2025 19:52:34 -0700 Subject: [PATCH 05/27] docs: Add example to `Iterator::take` with `by_ref` If you want to logically split an iterator after `n` items, you might first discover `take`. Before this change, you'd find that `take` consumes the iterator, and you'd probably be stuck. The answer involves `by_ref`, but that's hard to discover, especially since `by_ref` is a bit abstract and `Iterator` has many methods. After this change, you'd see the example showing `take` along with `by_ref`, which allows you to continue using the rest of the iterator. `by_ref` had a good example involving `take` already, so this change just duplicates that existing example under `take`. --- library/core/src/iter/traits/iterator.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index d9534a445980f..c68fd2115d689 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -1340,6 +1340,24 @@ pub trait Iterator { /// assert_eq!(iter.next(), Some(2)); /// assert_eq!(iter.next(), None); /// ``` + /// + /// Use [`by_ref`] to take from the iterator without consuming it, and then + /// continue using the original iterator: + /// + /// ``` + /// let mut words = ["hello", "world", "of", "Rust"].into_iter(); + /// + /// // Take the first two words. + /// let hello_world: Vec<_> = words.by_ref().take(2).collect(); + /// assert_eq!(hello_world, vec!["hello", "world"]); + /// + /// // Collect the rest of the words. + /// // We can only do this because we used `by_ref` earlier. + /// let of_rust: Vec<_> = words.collect(); + /// assert_eq!(of_rust, vec!["of", "Rust"]); + /// ``` + /// + /// [`by_ref`]: Iterator::by_ref #[inline] #[stable(feature = "rust1", since = "1.0.0")] fn take(self, n: usize) -> Take From 0369ccb5320da3871bc03e6b0df8afbbfac0c9b9 Mon Sep 17 00:00:00 2001 From: Janggun Lee Date: Mon, 14 Apr 2025 22:26:43 +0900 Subject: [PATCH 06/27] Fix some grammar errors and hyperlinks in doc for `trait Allocator` * "while until either" could also be changed to "for a while until either", but I just deleted "while". * fixed sentence with incorrect "at" and "has/have". * linked [*currently allocated*] similar to other methods. --- library/core/src/alloc/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/core/src/alloc/mod.rs b/library/core/src/alloc/mod.rs index 9805cee1c331e..9d608d5e83c40 100644 --- a/library/core/src/alloc/mod.rs +++ b/library/core/src/alloc/mod.rs @@ -90,7 +90,7 @@ impl fmt::Display for AllocError { /// # Safety /// /// Memory blocks that are [*currently allocated*] by an allocator, -/// must point to valid memory, and retain their validity while until either: +/// must point to valid memory, and retain their validity until either: /// - the memory block is deallocated, or /// - the allocator is dropped. /// @@ -112,7 +112,9 @@ pub unsafe trait Allocator { /// /// The returned block of memory remains valid as long as it is [*currently allocated*] and the shorter of: /// - the borrow-checker lifetime of the allocator type itself. - /// - as long as at the allocator and all its clones has not been dropped. + /// - as long as the allocator and all its clones have not been dropped. + /// + /// [*currently allocated*]: #currently-allocated-memory /// /// # Errors /// From 31cb737dee5193ba44ce26c429057f4681510fe7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Apr 2025 11:22:10 +0200 Subject: [PATCH 07/27] simd_select_bitmask: the 'padding' bits in the mask are just ignored --- library/core/src/intrinsics/simd.rs | 4 +--- src/tools/miri/src/intrinsics/simd.rs | 13 +------------ .../intrinsics/simd-select-bitmask-invalid.rs | 15 --------------- .../intrinsics/simd-select-bitmask-invalid.stderr | 15 --------------- .../miri/tests/pass/intrinsics/portable-simd.rs | 13 +++++++++++++ 5 files changed, 15 insertions(+), 45 deletions(-) delete mode 100644 src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs delete mode 100644 src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr diff --git a/library/core/src/intrinsics/simd.rs b/library/core/src/intrinsics/simd.rs index 9ac6ee8553558..facc2e7f0853b 100644 --- a/library/core/src/intrinsics/simd.rs +++ b/library/core/src/intrinsics/simd.rs @@ -577,11 +577,9 @@ pub unsafe fn simd_select(mask: M, if_true: T, if_false: T) -> T; /// For each element, if the bit in `mask` is `1`, select the element from /// `if_true`. If the corresponding bit in `mask` is `0`, select the element from /// `if_false`. +/// The remaining bits of the mask are ignored. /// /// The bitmask bit order matches `simd_bitmask`. -/// -/// # Safety -/// Padding bits must be all zero. #[rustc_intrinsic] #[rustc_nounwind] pub unsafe fn simd_select_bitmask(m: M, yes: T, no: T) -> T; diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index de5da6ec898a4..c9250ba1b818d 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -506,7 +506,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; let dest_len = u32::try_from(dest_len).unwrap(); - let bitmask_len = u32::try_from(bitmask_len).unwrap(); for i in 0..dest_len { let bit_i = simd_bitmask_index(i, dest_len, this.data_layout().endian); let mask = mask & 1u64.strict_shl(bit_i); @@ -517,17 +516,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let val = if mask != 0 { yes } else { no }; this.write_immediate(*val, &dest)?; } - for i in dest_len..bitmask_len { - // If the mask is "padded", ensure that padding is all-zero. - // This deliberately does not use `simd_bitmask_index`; these bits are outside - // the bitmask. It does not matter in which order we check them. - let mask = mask & 1u64.strict_shl(i); - if mask != 0 { - throw_ub_format!( - "a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits" - ); - } - } + // The remaining bits of the mask are ignored. } // Converts a "vector of bool" into a bitmask. "bitmask" => { diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs deleted file mode 100644 index 409098ac3b5df..0000000000000 --- a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![feature(core_intrinsics, repr_simd)] - -use std::intrinsics::simd::simd_select_bitmask; - -#[repr(simd)] -#[allow(non_camel_case_types)] -#[derive(Copy, Clone)] -struct i32x2([i32; 2]); - -fn main() { - unsafe { - let x = i32x2([0, 1]); - simd_select_bitmask(0b11111111u8, x, x); //~ERROR: bitmask less than 8 bits long must be filled with 0s for the remaining bits - } -} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr deleted file mode 100644 index 9acb51d8c5f36..0000000000000 --- a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: Undefined Behavior: a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits - --> tests/fail/intrinsics/simd-select-bitmask-invalid.rs:LL:CC - | -LL | simd_select_bitmask(0b11111111u8, x, x); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ a SIMD bitmask less than 8 bits long must be filled with 0s for the remaining bits - | - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: - = note: inside `main` at tests/fail/intrinsics/simd-select-bitmask-invalid.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to 1 previous error - diff --git a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs index cc753dac2156f..e14ce51f35a3f 100644 --- a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs +++ b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs @@ -331,6 +331,19 @@ fn simd_mask() { ); assert_eq!(selected1, i32x4::from_array([0, 0, 0, 1])); assert_eq!(selected2, selected1); + // Non-zero "padding" (the extra bits) is also allowed. + let selected1 = simd_select_bitmask::( + if cfg!(target_endian = "little") { 0b11111000 } else { 0b11110001 }, + i32x4::splat(1), // yes + i32x4::splat(0), // no + ); + let selected2 = simd_select_bitmask::<[u8; 1], _>( + if cfg!(target_endian = "little") { [0b11111000] } else { [0b11110001] }, + i32x4::splat(1), // yes + i32x4::splat(0), // no + ); + assert_eq!(selected1, i32x4::from_array([0, 0, 0, 1])); + assert_eq!(selected2, selected1); } // Non-power-of-2 multi-byte mask. From 3a372e39ca54339925bf01ead93b3c1dc01078d2 Mon Sep 17 00:00:00 2001 From: xizheyin Date: Sun, 20 Apr 2025 14:32:33 +0800 Subject: [PATCH 08/27] std: mention `remove_dir_all` can emit `DirectoryNotEmpty` when concurrently written into Signed-off-by: xizheyin --- library/std/src/fs.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 801baf3d99072..0e306d23dd936 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2874,6 +2874,8 @@ pub fn remove_dir>(path: P) -> io::Result<()> { /// /// Consider ignoring the error if validating the removal is not required for your use case. /// +/// This function may return [`io::ErrorKind::DirectoryNotEmpty`] if the directory is concurrently +/// written into, which typically indicates some contents were removed but not all. /// [`io::ErrorKind::NotFound`] is only returned if no removal occurs. /// /// [`fs::remove_file`]: remove_file From 2b8e9b508c6eb60e1ee37509f9dead90bd56449a Mon Sep 17 00:00:00 2001 From: xizheyin Date: Fri, 25 Apr 2025 15:17:19 +0800 Subject: [PATCH 09/27] Enable [behind-upstream] triagebot option Signed-off-by: xizheyin --- src/doc/rustc-dev-guide/triagebot.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/doc/rustc-dev-guide/triagebot.toml b/src/doc/rustc-dev-guide/triagebot.toml index 12aa0b7b8ff15..61093ecaaadf7 100644 --- a/src/doc/rustc-dev-guide/triagebot.toml +++ b/src/doc/rustc-dev-guide/triagebot.toml @@ -9,3 +9,6 @@ allow-unauthenticated = [ # Automatically close and reopen PRs made by bots to run CI on them [bot-pull-requests] + +[behind-upstream] +days-threshold = 7 \ No newline at end of file From 451d73fbe2cbb4b03e4a8c08d44074b53c669035 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Mon, 28 Apr 2025 06:49:13 +0200 Subject: [PATCH 10/27] use repo name in push pr title I found "Rustc dev guide subtree update awkward" --- src/doc/rustc-dev-guide/josh-sync/src/sync.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/josh-sync/src/sync.rs b/src/doc/rustc-dev-guide/josh-sync/src/sync.rs index cd64be6367032..41d96397faaba 100644 --- a/src/doc/rustc-dev-guide/josh-sync/src/sync.rs +++ b/src/doc/rustc-dev-guide/josh-sync/src/sync.rs @@ -194,7 +194,7 @@ impl GitSync { ); println!( // Open PR with `subtree update` title to silence the `no-merges` triagebot check - " https://github.com/{UPSTREAM_REPO}/compare/{github_user}:{branch}?quick_pull=1&title=Rustc+dev+guide+subtree+update&body=r?+@ghost" + " https://github.com/{UPSTREAM_REPO}/compare/{github_user}:{branch}?quick_pull=1&title=rustc-dev-guide+subtree+update&body=r?+@ghost" ); drop(josh); From 3d23917b2d331b92e59b73a50098f9f3b2bf5a9c Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 28 Apr 2025 11:29:42 -0700 Subject: [PATCH 11/27] Add an example of the example of an edition migration lint It was observed that some people were missing the `edition20xx` rustdoc attribute. Although this probably won't solve that problem, I'd still like to highlight it as something to be aware of. --- src/doc/rustc-dev-guide/src/guides/editions.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/guides/editions.md b/src/doc/rustc-dev-guide/src/guides/editions.md index ea207167791b7..750631dfe565d 100644 --- a/src/doc/rustc-dev-guide/src/guides/editions.md +++ b/src/doc/rustc-dev-guide/src/guides/editions.md @@ -193,6 +193,23 @@ When a user runs `cargo fix --edition`, cargo will pass the `--force-warn rust-2 flag to force all of these lints to appear during the edition migration. Cargo also passes `--cap-lints=allow` so that no other lints interfere with the edition migration. +Make sure that the example code sets the correct edition. The example should illustrate the previous edition, and show what the migration warning would look like. For example, this lint for a 2024 migration shows an example in 2021: + +```rust,ignore +declare_lint! { + /// The `keyword_idents_2024` lint detects ... + /// + /// ### Example + /// + /// ```rust,edition2021 + /// #![warn(keyword_idents_2024)] + /// fn gen() {} + /// ``` + /// + /// {{produces}} +} +``` + Migration lints can be either `Allow` or `Warn` by default. If it is `Allow`, users usually won't see this warning unless they are doing an edition migration manually or there is a problem during the migration. From b0e675bc5251bae6b4bf955d4dfdc3ee5b6f4922 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 28 Apr 2025 11:30:33 -0700 Subject: [PATCH 12/27] Add documentation on how to migration the edition of the standard library Based on lessons learned from 2024. There's probably still more details to say here since it was a ton of work. These are the major points that I remember. --- src/doc/rustc-dev-guide/src/guides/editions.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/guides/editions.md b/src/doc/rustc-dev-guide/src/guides/editions.md index 750631dfe565d..99a61d29af48c 100644 --- a/src/doc/rustc-dev-guide/src/guides/editions.md +++ b/src/doc/rustc-dev-guide/src/guides/editions.md @@ -351,3 +351,21 @@ In general it is recommended to avoid these special cases except for very high v [into-iter]: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html [panic-macro]: https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html [`non_fmt_panics`]: https://doc.rust-lang.org/nightly/rustc/lints/listing/warn-by-default.html#non-fmt-panics + +### Migrating the standard library edition + +Updating the edition of the standard library itself roughly involves the following process: + +- Wait until the newly stabilized edition has reached beta and the bootstrap compiler has been updated. +- Apply migration lints. This can be an involved process since some code is in external submodules[^std-submodules], and the standard library makes heavy use of conditional compilation. Also, running `cargo fix --edition` can be impractical on the standard library itself. One approach is to individually add `#![warn(...)]` at the top of each crate for each lint, run `./x check library`, apply the migrations, remove the `#![warn(...)]` and commit each migration separately. You'll likely need to run `./x check` with `--target` for many different targets to get full coverage (otherwise you'll likely spend days or weeks getting CI to pass)[^ed-docker]. See also the [advanced migration guide] for more tips. + - Apply migrations to [`backtrace-rs`]. [Example for 2024](https://github.com/rust-lang/backtrace-rs/pull/700). Note that this doesn't update the edition of the crate itself because that is published independently on crates.io, and that would otherwise restrict the minimum Rust version. Consider adding some `#![deny()]` attributes to avoid regressions until its edition gets updated. + - Apply migrations to [`stdarch`], and update its edition, and formatting. [Example for 2024](https://github.com/rust-lang/stdarch/pull/1710). + - Post PRs to update the backtrace and stdarch submodules, and wait for those to land. + - Apply migration lints to the standard library crates, and update their edition. I recommend working one crate at a time starting with `core`. [Example for 2024](https://github.com/rust-lang/rust/pull/138162). + +[^std-submodules]: This will hopefully change in the future to pull these submodules into `rust-lang/rust`. +[^ed-docker]: You'll also likely need to do a lot of testing for different targets, and this is where [docker testing](../tests/docker.md) comes in handy. + +[advanced migration guide]: https://doc.rust-lang.org/nightly/edition-guide/editions/advanced-migrations.html +[`backtrace-rs`]: https://github.com/rust-lang/backtrace-rs/ +[`stdarch`]: https://github.com/rust-lang/stdarch/ From 0aae3caf240b985d02de541cb3f2990eb6bbe927 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 28 Apr 2025 11:41:17 -0700 Subject: [PATCH 13/27] Update mdbook to 0.4.48 This updates to the latest version of mdbook which has had a variety of fixes of new features since the last update. Changelog: https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md#mdbook-0448 --- src/doc/rustc-dev-guide/.github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/.github/workflows/ci.yml b/src/doc/rustc-dev-guide/.github/workflows/ci.yml index 415d0dc397dc9..daf5223cbd4ac 100644 --- a/src/doc/rustc-dev-guide/.github/workflows/ci.yml +++ b/src/doc/rustc-dev-guide/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: if: github.repository == 'rust-lang/rustc-dev-guide' runs-on: ubuntu-latest env: - MDBOOK_VERSION: 0.4.21 + MDBOOK_VERSION: 0.4.48 MDBOOK_LINKCHECK2_VERSION: 0.9.1 MDBOOK_MERMAID_VERSION: 0.12.6 MDBOOK_TOC_VERSION: 0.11.2 From 50500376b3f6b69be2b8daedacd11709849fb5bc Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 28 Apr 2025 11:31:09 -0700 Subject: [PATCH 14/27] Add documentation on how to stabilize the compiler edition This adds documentation on how to stabilize the edition in the compiler. --- .../rustc-dev-guide/src/guides/editions.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/doc/rustc-dev-guide/src/guides/editions.md b/src/doc/rustc-dev-guide/src/guides/editions.md index 99a61d29af48c..9a92d4ebcb510 100644 --- a/src/doc/rustc-dev-guide/src/guides/editions.md +++ b/src/doc/rustc-dev-guide/src/guides/editions.md @@ -369,3 +369,22 @@ Updating the edition of the standard library itself roughly involves the followi [advanced migration guide]: https://doc.rust-lang.org/nightly/edition-guide/editions/advanced-migrations.html [`backtrace-rs`]: https://github.com/rust-lang/backtrace-rs/ [`stdarch`]: https://github.com/rust-lang/stdarch/ + +## Stabilizing an edition + +After the edition team has given the go-ahead, the process for stabilizing an edition is roughly: + +- Update [`LATEST_STABLE_EDITION`]. +- Update [`Edition::is_stable`]. +- Hunt and find any document that refers to edition by number, and update it: + - [`--edition` flag](https://github.com/rust-lang/rust/blob/master/src/doc/rustc/src/command-line-arguments.md#--edition-specify-the-edition-to-use) + - [Rustdoc attributes](https://github.com/rust-lang/rust/blob/master/src/doc/rustdoc/src/write-documentation/documentation-tests.md#attributes) +- Clean up any tests that use the `//@ edition` header to remove the `-Zunstable-options` flag to ensure they are indeed stable. Note: Ideally this should be automated, see [#133582]. +- Bless any tests that change. +- Update `lint-docs` to default to the new edition. + +See [example for 2024](https://github.com/rust-lang/rust/pull/133349). + +[`LATEST_STABLE_EDITION`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/edition/constant.LATEST_STABLE_EDITION.html +[`Edition::is_stable`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/edition/enum.Edition.html#method.is_stable +[#133582]: https://github.com/rust-lang/rust/issues/133582 From c466cd01d8b6fdac7c506bad2b035df653bfd921 Mon Sep 17 00:00:00 2001 From: Stan Manilov Date: Tue, 29 Apr 2025 16:39:54 +0300 Subject: [PATCH 15/27] Update compiler-src.md Refactor the dependency structure from a nested unordered list to a single-level ordered list. IMO, this is clearer, but happy to close this PR without merging, if the change is not desired. --- src/doc/rustc-dev-guide/src/compiler-src.md | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/compiler-src.md b/src/doc/rustc-dev-guide/src/compiler-src.md index c538fc8b788d7..0d3cbebb4da09 100644 --- a/src/doc/rustc-dev-guide/src/compiler-src.md +++ b/src/doc/rustc-dev-guide/src/compiler-src.md @@ -62,21 +62,20 @@ huge. There is also the `rustc` crate which is the actual binary (i.e. the [`rustc_driver`] crate, which drives the various parts of compilation in other crates. -The dependency structure of these crates is complex, but roughly it is +The dependency order of these crates is complex, but roughly it is something like this: -- `rustc` (the binary) calls [`rustc_driver::main`][main]. - - [`rustc_driver`] depends on a lot of other crates, but the main one is - [`rustc_interface`]. - - [`rustc_interface`] depends on most of the other compiler crates. It - is a fairly generic interface for driving the whole compilation. - - Most of the other `rustc_*` crates depend on [`rustc_middle`], - which defines a lot of central data structures in the compiler. - - [`rustc_middle`] and most of the other crates depend on a - handful of crates representing the early parts of the - compiler (e.g. the parser), fundamental data structures (e.g. - [`Span`]), or error reporting: [`rustc_data_structures`], - [`rustc_span`], [`rustc_errors`], etc. +1. `rustc` (the binary) calls [`rustc_driver::main`][main]. +1. [`rustc_driver`] depends on a lot of other crates, but the main one is + [`rustc_interface`]. +1. [`rustc_interface`] depends on most of the other compiler crates. It is a + fairly generic interface for driving the whole compilation. +1. Most of the other `rustc_*` crates depend on [`rustc_middle`], which defines + a lot of central data structures in the compiler. +1. [`rustc_middle`] and most of the other crates depend on a handful of crates + representing the early parts of the compiler (e.g. the parser), fundamental + data structures (e.g. [`Span`]), or error reporting: + [`rustc_data_structures`], [`rustc_span`], [`rustc_errors`], etc. [`rustc_data_structures`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_data_structures/index.html [`rustc_driver`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver/index.html From a2b3f11700821d778d4dfbc3651a61a0f0f6f3be Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Tue, 29 Apr 2025 22:12:27 +0800 Subject: [PATCH 16/27] Filter out LoongArch features not supported by the current LLVM version --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index ae1bdac1655d3..c17b99f19468b 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -273,6 +273,12 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option None, // only existed in 18 ("arm", "fp16") => Some(LLVMFeature::new("fullfp16")), // Filter out features that are not supported by the current LLVM version + ("loongarch64", "div32" | "lam-bh" | "lamcas" | "ld-seq-sa" | "scq") + if get_version().0 < 20 => + { + None + } + // Filter out features that are not supported by the current LLVM version ("riscv32" | "riscv64", "zacas") if get_version().0 < 20 => None, // Enable the evex512 target feature if an avx512 target feature is enabled. ("x86", s) if s.starts_with("avx512") => { From 74b55b4b865a6ca882e9006bcf7b87f5eb845f4b Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Tue, 29 Apr 2025 22:15:11 +0800 Subject: [PATCH 17/27] Add comment to remind filtering unsupported features when adding new ones --- compiler/rustc_target/src/target_features.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index 69c8b9119ab23..007bfea887c80 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -102,6 +102,9 @@ impl Stability { // check whether they're named already elsewhere in rust // e.g. in stdarch and whether the given name matches LLVM's // if it doesn't, to_llvm_feature in llvm_util in rustc_codegen_llvm needs to be adapted. +// Additionally, if the feature is not available in older version of LLVM supported by the current +// rust, the same function must be updated to filter out these features to avoid triggering +// warnings. // // Also note that all target features listed here must be purely additive: for target_feature 1.1 to // be sound, we can never allow features like `+soft-float` (on x86) to be controlled on a From e2fb99c97eaa30927b3b36de27d242ad673c491b Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 29 Apr 2025 15:28:04 +0100 Subject: [PATCH 18/27] Introduce a normalization chapter --- src/doc/rustc-dev-guide/src/SUMMARY.md | 2 +- src/doc/rustc-dev-guide/src/normalization.md | 309 ++++++++++++++++++ .../src/solve/normalization.md | 127 ------- .../src/solve/significant-changes.md | 2 +- 4 files changed, 311 insertions(+), 129 deletions(-) create mode 100644 src/doc/rustc-dev-guide/src/normalization.md delete mode 100644 src/doc/rustc-dev-guide/src/solve/normalization.md diff --git a/src/doc/rustc-dev-guide/src/SUMMARY.md b/src/doc/rustc-dev-guide/src/SUMMARY.md index 68112d06167a0..6aa37fa9ec54b 100644 --- a/src/doc/rustc-dev-guide/src/SUMMARY.md +++ b/src/doc/rustc-dev-guide/src/SUMMARY.md @@ -156,6 +156,7 @@ - [ADTs and Generic Arguments](./ty_module/generic_arguments.md) - [Parameter types/consts/regions](./ty_module/param_ty_const_regions.md) - [`TypeFolder` and `TypeFoldable`](./ty-fold.md) +- [Aliases and Normalization](./normalization.md) - [Typing/Param Envs](./typing_parameter_envs.md) - [Type inference](./type-inference.md) - [Trait solving](./traits/resolution.md) @@ -175,7 +176,6 @@ - [Coinduction](./solve/coinduction.md) - [Caching](./solve/caching.md) - [Proof trees](./solve/proof-trees.md) - - [Normalization](./solve/normalization.md) - [Opaque types](./solve/opaque-types.md) - [Significant changes and quirks](./solve/significant-changes.md) - [`Unsize` and `CoerceUnsized` traits](./traits/unsize.md) diff --git a/src/doc/rustc-dev-guide/src/normalization.md b/src/doc/rustc-dev-guide/src/normalization.md new file mode 100644 index 0000000000000..43ee05b8b57ea --- /dev/null +++ b/src/doc/rustc-dev-guide/src/normalization.md @@ -0,0 +1,309 @@ +# Aliases and Normalization + + + +## Aliases + +In Rust there are a number of types that are considered equal to some "underlying" type, for example inherent associated types, trait associated types, free type aliases (`type Foo = u32`), and opaque types (`-> impl RPIT`). We consider such types to be "aliases", alias types are represented by the [`TyKind::Alias`][tykind_alias] variant, with the kind of alias tracked by the [`AliasTyKind`][aliaskind] enum. + +Normalization is the process of taking these alias types and replacing them with the underlying type that they are equal to. For example given some type alias `type Foo = u32`, normalizing `Foo` would give `u32`. + +The concept of an alias is not unique to *types* and the concept also applies to constants/const generics. However, right now in the compiler we don't really treat const aliases as a "first class concept" so this chapter mostly discusses things in the context of types (even though the concepts transfer just fine). + +[tykind_alias]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/enum.TyKind.html#variant.Alias +[aliaskind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/enum.AliasTyKind.html + +### Rigid, Ambiguous and Unnormalized Aliases + +Aliases can either be "rigid", "ambiguous", or simply unnormalized. + +We consider types to be rigid if their "shape" isn't going to change, for example `Box` is rigid as no amount of normalization can turn a `Box` into a `u32`, whereas ` as Iterator>::Item` is not rigid as it can be normalized to `u32`. + +Aliases are rigid when we will never be able to normalize them further. A concrete example of a *rigid* alias would be `::Item` in an environment where there is no `T: Iterator` bound, only a `T: Iterator` bound: +```rust +fn foo() { + // This alias is *rigid* + let _: ::Item; +} + +fn bar>() { + // This alias is *not* rigid as it can be normalized to `u32` + let _: ::Item; +} +``` + +When an alias can't yet be normalized but may wind up normalizable in the [current environment](./typing_parameter_envs.md), we consider it to be an "ambiguous" alias. This can occur when an alias contains inference variables which prevent being able to determine how the trait is implemented: +```rust +fn foo() { + // This alias is considered to be "ambiguous" + let _: <_ as Iterator>::Item; +} +``` + +The reason we call them "ambiguous" aliases is because its *ambiguous* whether this is a rigid alias or not. + +The source of the `_: Iterator` trait impl is *ambiguous* (i.e. unknown), it could be some `impl Iterator for u32` or it could be some `T: Iterator` trait bound, we don't know yet. Depending on why `_: Iterator` holds the alias could be an unnormalized alias or it could be a rigid alias; it's *ambiguous* what kind of alias this is. + +Finally, an alias can just be unnormalized, ` as IntoIterator>::Iter` is an unnormalized alias as it can already be normalized to `std::vec::IntoIter`, it just hasn't been done yet. + +--- + +It is worth noting that Free and Inherent aliases cannot be rigid or ambiguous as naming them also implies having resolved the definition of the alias, which specifies the underlying type of the alias. + +### Diverging Aliases + +An alias is considered to "diverge" if its definition does not specify an underlying non-alias type to normalize to. A concrete example of diverging aliases: +```rust +type Diverges = Diverges; + +trait Trait { + type DivergingAssoc; +} +impl Trait for () { + type DivergingAssoc = <() as Trait>::DivergingAssoc; +} +``` +In this example both `Diverges` and `DivergingAssoc` are "trivial" cases of diverging type aliases where they have been defined as being equal to themselves. There is no underlying type that `Diverges` can ever be normalized to. + +We generally try to error when diverging aliases are defined, but this is entirely a "best effort" check. In the previous example the definitions are "simple enough" to be detected and so errors are emitted. However, in more complex cases, or cases where only some instantiations of generic parameters would result in a diverging alias, we don't emit an error: +```rust +trait Trait { + type DivergingAssoc; +} +impl Trait for T { + // This alias always diverges but we don't emit an error because + // the compiler can't "see" that. + type DivergingAssoc = ::DivergingAssoc; +} +``` + +Ultimately this means that we have no guarantee that aliases in the type system are non-diverging. As aliases may only diverge for some specific generic arguments, it also means that we only know whether an alias diverges once it is fully concrete. This means that codegen/const-evaluation also has to handle diverging aliases: +```rust +trait Trait { + type Diverges; +} +impl Trait for T { + type Diverges = ::Diverges; +} + +fn foo() { + let a: T::Diverges; +} + +fn main() { + foo::<()>(); +} +``` +In this example we only encounter an error from the diverging alias during codegen of `foo::<()>`, if the call to `foo` is removed then no compilation error will be emitted. + +### Opaque Types + +Opaque types are a relatively special kind of alias, and are covered in their own chapter: [Opaque types](./opaque-types-type-alias-impl-trait.md). + +### Const Aliases + +Unlike type aliases, const aliases are not represented directly in the type system, instead const aliases are always an anonymous body containing a path expression to a const item. This means that the only "const alias" in the type system is an anonymous unevaluated const body. + +As such there is no `ConstKind::Alias(AliasCtKind::Projection/Inherent/Free, _)`, instead we only have `ConstKind::Unevaluated` which is used for representing anonymous constants. + +```rust +fn foo() {} + +const FREE_CONST: usize = 1 + 1; + +fn bar() { + foo::<{ FREE_CONST }>(); + // The const arg is represented with some anonymous constant: + // ```pseudo-rust + // const ANON: usize = FREE_CONST; + // foo::(); + // ``` +} +``` + +This is likely to change as const generics functionality is improved, for example `feature(associated_const_equality)` and `feature(min_generic_const_args)` both require handling const aliases similarly to types (without an anonymous constant wrapping all const args). + +## What is Normalization + +### Structural vs Deep normalization + +There are two forms of normalization, structural (sometimes called *shallow*) and deep. Structural normalization should be thought of as only normalizing the "outermost" part of a type. On the other hand deep normalization will normalize *all* aliases in a type. + +In practice structural normalization can result in more than just the outer layer of the type being normalized[^1], but this behaviour should not be relied upon. Unnormalizable non-rigid aliases making use of bound variables (`for<'a>`) cannot be normalized by either kind of normalization. + +As an example: conceptually, structurally normalizing the type `Vec<::Assoc>` would be a no-op, whereas deeply normalizing would give `Vec`. In practice even structural normalization would give `Vec`, though, again, this should not be relied upon. + +Changing the alias to use bound variables will result in different behaviour; `Vec fn(<&'a u8 as Identity>::Assoc)>` would result in no change when structurally normalized, but would result in `Vec fn(&'a u8)>` when deeply normalized. + +### Core normalization logic + +Structurally normalizing aliases is a little bit more nuanced than replacing the alias with whatever it is defined as being equal to in its definition; the result of normalizing an alias should either be a rigid type or an inference variable (which will later be inferred to a rigid type). To accomplish this we do two things: + +First, when normalizing an ambiguous alias it is normalized to an inference variable instead of leaving it as-is, this has two main effects: +- Even though an inference variable is not a rigid type, it will always wind up inferred *to* a rigid type so we ensure that the result of normalization will not need to be normalized again +- Inference variables are used in all cases where a type is non-rigid, allowing the rest of the compiler to not have to deal with *both* ambiguous aliases *and* inference variables + +Secondly, instead of having normalization directly return the type specified in the definition of the alias, we normalize the type first before returning it[^1]. We do this so that normalization is idempotent/callers do not need to run it in a loop. + +```rust +#![feature(lazy_type_alias)] + +type Foo = Bar; +type Bar = ::Item; + +fn foo() { + let a_: Foo<_>; +} +``` + +In this example: +- Normalizing `Foo` would result in `Bar`, except we want to normalize aliases in the type `Foo` is defined as equal to +- Normalizing `Bar` would result in `::Item`, except, again, we want to normalize aliases in the type `Bar` is defined as equal to +- Normalizing `::Item` results in some new inference variable `?y`, as `::Item` is an ambiguous alias +- The final result is that normalizing `Foo` results in `?y` + +[^1]: In the new solver this is done implicitly + +## How to normalize + +When interfacing with the type system it will often be the case that it's necessary to request a type be normalized. There are a number of different entry points to the underlying normalization logic and each entry point should only be used in specific parts of the compiler. + +An additional complication is that the compiler is currently undergoing a transition from the old trait solver to the new trait solver. As part of this transition our approach to normalization in the compiler has changed somewhat significantly, resulting in some normalization entry points being "old solver only" slated for removal in the long-term once the new solver has stabilized. + +Here is a rough overview of the different entry points to normalization in the compiler: +- `infcx.at.structurally_normalize` +- `infcx.at.(deeply_)?normalize` +- `infcx.query_normalize` +- `tcx.normalize_erasing_regions` +- `traits::normalize_with_depth(_to)` +- `EvalCtxt::structurally_normalize` + +### Outside of the trait solver + +The [`InferCtxt`][infcx] type exposes the "main" ways to normalize during analysis: [`normalize`][normalize], [`deeply_normalize`][deeply_normalize] and [`structurally_normalize`][structurally_normalize]. These functions are often wrapped and re-exposed on various `InferCtxt` wrapper types, such as [`FnCtxt`][fcx] or [`ObligationCtxt`][ocx] with minor API tweaks to handle some arguments or parts of the return type automatically. + +#### Structural `InferCtxt` normalization + +[`infcx.at.structurally_normalize`][structurally_normalize] exposes structural normalization that is able to handle inference variables and regions. It should generally be used whenever inspecting the kind of a type. + +Inside of HIR Typeck there is a related method of normalization- [`fcx.structurally_resolve`][structurally_resolve], which will error if the type being resolved is an unresolved inference variable. When the new solver is enabled it will also attempt to structurally normalize the type. + +Due to this there is a pattern in HIR typeck where a type is first normalized via `normalize` (only normalizing in the old solver), and then `structurally_resolve`'d (only normalizing in the new solver). This pattern should be preferred over calling `structurally_normalize` during HIR typeck as `structurally_resolve` will attempt to make inference progress by evaluating goals whereas `structurally_normalize` does not. + +#### Deep `InferCtxt` normalization + +##### `infcx.at.(deeply_)?normalize` + +There are two ways to deeply normalize with an `InferCtxt`, `normalize` and `deeply_normalize`. The reason for this is that `normalize` is a "legacy" normalization entry point used only by the old solver, whereas `deeply_normalize` is intended to be the long term way to deeply normalize. Both of these methods can handle regions. + +When the new solver is stabilized the `infcx.at.normalize` function will be removed and everything will have been migrated to the new deep or structural normalization methods. For this reason the `normalize` function is a no-op under the new solver, making it suitable only when the old solver needs normalization but the new solver does not. + +Using `deeply_normalize` will result in errors being emitted when encountering ambiguous aliases[^1] as it is not possible to support normalizing *all* ambiguous aliases to inference variables[^2]. `deeply_normalize` should generally only be used in cases where we do not expect to encounter ambiguous aliases, for example when working with types from item signatures. + +[^1]: There is a subtle difference in how ambiguous aliases in binders are handled between old and new solver. In the old solver we fail to error on some ambiguous aliases inside of higher ranked types whereas the new solver correctly errors. + +[^2]: Ambiguous aliases inside of binders cannot be normalized to inference variables, this will be covered more later. + +##### `infcx.query_normalize` + +[`infcx.query_normalize`][query_norm] is very rarely used, it has almost all the same restrictions as `normalize_erasing_regions` (cannot handle inference variables, no diagnostics support) with the main difference being that it retains lifetime information. For this reason `normalize_erasing_regions` is the better choice in almost all circumstances as it is more efficient due to caching lifetime-erased queries. + +In practice `query_normalize` is used for normalization in the borrow checker, and elsewhere as a performance optimization over `infcx.normalize`. Once the new solver is stabilized it is expected that `query_normalize` can be removed from the compiler as the new solvers normalization implementation should be performant enough for it to not be a performance regression. + +##### `tcx.normalize_erasing_regions` + +[`normalize_erasing_regions`][norm_erasing_regions] is generally used by parts of the compiler that are not doing type system analysis. This normalization entry point does not handle inference variables, lifetimes, or any diagnostics. Lints and codegen make heavy use of this entry point as they typically are working with fully inferred aliases that can be assumed to be well formed (or at least, are not responsible for erroring on). + +[query_norm]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/at/struct.At.html#method.query_normalize +[norm_erasing_regions]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.normalize_erasing_regions +[normalize]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/at/struct.At.html#method.normalize +[deeply_normalize]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/traits/normalize/trait.NormalizeExt.html#tymethod.deeply_normalize +[structurally_normalize]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/traits/trait.StructurallyNormalizeExt.html#tymethod.structurally_normalize_ty +[infcx]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/infer/struct.InferCtxt.html +[fcx]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/fn_ctxt/struct.FnCtxt.html +[ocx]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/traits/struct.ObligationCtxt.html +[structurally_resolve]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/fn_ctxt/struct.FnCtxt.html#method.structurally_resolve_type + +### Inside of the trait solver + +[`traits::normalize_with_depth(_to)`][norm_with_depth] and [`EvalCtxt::structurally_normalize`][eval_ctxt_structural_norm] are only used by the internals of the trait solvers (old and new respectively). It is effectively a raw entry point to the internals of how normalization is implemented by each trait solver. Other normalization entry points cannot be used from within the internals of trait solving as it wouldn't handle goal cycles and recursion depth correctly. + +[norm_with_depth]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/traits/normalize/fn.normalize_with_depth.html +[eval_ctxt_structural_norm]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_next_trait_solver/solve/struct.EvalCtxt.html#method.structurally_normalize_term + +## When/Where to normalize (Old vs New solver) + +One of the big changes between the old and new solver is our approach to when we expect aliases to be normalized. + +### Old solver + +All types are expected to be normalized as soon as possible, so that all types encountered in the type system are either rigid or an inference variable (which will later be inferred to a rigid term). + +As a concrete example: equality of aliases is implemented by assuming they're rigid and recursively equating the generic arguments of the alias. + +### New solver + +It's expected that all types potentially contain ambiguous or unnormalized aliases. Whenever an operation is performed that requires aliases to be normalized, it's the responsibility of that logic to normalize the alias (this means that matching on `ty.kind()` pretty much always has to structurally normalize first). + +As a concrete example: equality of aliases is implemented by a custom goal kind ([`PredicateKind::AliasRelate`][aliasrelate]) so that it can handle normalization of the aliases itself instead of assuming all alias types being equated are rigid. + +Despite this approach we still deeply normalize during [writeback][writeback] for performance/simplicity, so that types in the MIR can still be assumed to have been deeply normalized. + +[aliasrelate]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.PredicateKind.html#variant.AliasRelate +[writeback]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir_typeck/writeback/index.html + +--- + +There were a few main issues with the old solver's approach to normalization that motivated changing things in the new solver: + +### Missing normalization calls + +It was a frequent occurrence that normalization calls would be missing, resulting in passing unnormalized types to APIs expecting everything to already be normalized. Treating ambiguous or unnormalized aliases as rigid would result in all sorts of weird errors from aliases not being considered equal to one another, or surprising inference guidance from equating unnormalized aliases' generic arguments. + +### Normalizing parameter environments + +Another problem was that it was not possible to normalize `ParamEnv`s correctly in the old solver as normalization itself would expect a normalized `ParamEnv` in order to give correct results. See the chapter on `ParamEnv`s for more information: [`Typing/ParamEnv`s: Normalizing all bounds](./typing_parameter_envs.md#normalizing-all-bounds) + +### Unnormalizable non-rigid aliases in higher ranked types + +Given a type such as `for<'a> fn(::Assoc>)`, it is not possible to correctly handle this with the old solver's approach to normalization. + +If we were to normalize it to `for<'a> fn(?y)` and register a goal to normalize `for<'a> >::Assoc -> ?y`, this would result in errors in cases where `>::Assoc` normalized to `&'a u32`. The inference variable `?y` would be in a lower [universe][universes] than the placeholders made when instantiating the `for<'a>` binder. + +Leaving the alias unnormalized would also be wrong as the old solver expects all aliases to be rigid. This was a soundness bug before the new solver was stabilized in coherence: [relating projection substs is unsound during coherence](https://github.com/rust-lang/rust/issues/102048). + +Ultimately this means that it is not always possible to ensure all aliases inside of a value are rigid. + +[universes]: https://rustc-dev-guide.rust-lang.org/borrow_check/region_inference/placeholders_and_universes.html#what-is-a-universe +[deeply_normalize]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_trait_selection/traits/normalize/trait.NormalizeExt.html#tymethod.deeply_normalize + +## Handling uses of diverging aliases + +Diverging aliases, like ambiguous aliases, are normalized to inference variables. As normalizing diverging aliases results in trait solver cycles, it always results in an error in the old solver. In the new solver it only results in an error if we wind up requiring all goals to hold in the current context. E.g. normalizing diverging aliases during HIR typeck will result in an error in both solvers. + +Alias well formedness doesn't require that the alias doesn't diverge[^1], this means that checking an alias is well formed isn't sufficient to cause an error to be emitted for diverging aliases; actually attempting to normalize the alias is required. + +Erroring on diverging aliases being a side effect of normalization means that it is very *arbitrary* whether we actually emit an error, it also differs between the old and new solver as we now normalize in less places. + +An example of the ad-hoc nature of erroring on diverging aliases causing "problems": +```rust +trait Trait { + type Diverges; +} + +impl Trait for T { + type Diverges = D::Diverges; +} + +struct Bar::Diverges>(Box); +``` + +In this example a diverging alias is used but we happen to not emit an error as we never explicitly normalize the defaults of generic parameters. If the `?Sized` opt out is removed then an error is emitted because we wind up happening to normalize a `::Diverges: Sized` goal which as a side effect results in erroring about the diverging alias. + +Const aliases differ from type aliases a bit here; well formedness of const aliases requires that they can be successfully evaluated (via [`ConstEvaluatable`][const_evaluatable] goals). This means that simply checking well formedness of const arguments is sufficient to error if they would fail to evaluate. It is somewhat unclear whether it would make sense to adopt this for type aliases too or if const aliases should stop requiring this for well formedness[^2]. + +[^1]: As checking aliases are non-diverging cannot be done until they are fully concrete, this would either imply that we cant check aliases are well formed before codegen/const-evaluation or that aliases would go from being well-formed to not well-formed after monomorphization. + +[^2]: Const aliases certainly wouldn't be *less* sound than type aliases if we stopped doing this + +[const_evaluatable]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.ClauseKind.html#variant.ConstEvaluatable \ No newline at end of file diff --git a/src/doc/rustc-dev-guide/src/solve/normalization.md b/src/doc/rustc-dev-guide/src/solve/normalization.md deleted file mode 100644 index 99dc20c46b5d4..0000000000000 --- a/src/doc/rustc-dev-guide/src/solve/normalization.md +++ /dev/null @@ -1,127 +0,0 @@ -# Normalization in the new solver - -> FIXME: Normalization has been changed significantly since this chapter was written. - -With the new solver we've made some fairly significant changes to normalization when compared -to the existing implementation. - -We now differentiate between "one-step normalization", "structural normalization" and -"deep normalization". - -## One-step normalization - -One-step normalization is implemented via `NormalizesTo` goals. Unlike other goals -in the trait solver, `NormalizesTo` always expects the term to be an unconstrained -inference variable[^opaques]. Think of it as a function, taking an alias as input -and returning its underlying value. If the alias is rigid, `NormalizesTo` fails and -returns `NoSolution`. This is the case for `::Assoc` if there's a `T: Trait` -where-bound and for opaque types with `Reveal::UserFacing` unless they are in the -defining scope. We must not treat any aliases as rigid in coherence. - -The underlying value may itself be an unnormalized alias, e.g. -`NormalizesTo(<<() as Id>::This as Id>::This)` only returns `<() as Id>::This`, -even though that alias can be further normalized to `()`. As the term is -always an unconstrained inference variable, the expected term cannot influence -normalization, see [trait-system-refactor-initiative#22] for more. - -Only ever computing `NormalizesTo` goals with an unconstrained inference variable -requires special solver support. It is only used by `AliasRelate` goals and pending -`NormalizesTo` goals are tracked separately from other goals: [source][try-eval-norm]. -As the expected term is always erased in `NormalizesTo`, we have to return its -ambiguous nested goals to its caller as not doing so weakens inference. See -[#122687] for more details. - -[trait-system-refactor-initiative#22]: https://github.com/rust-lang/trait-system-refactor-initiative/issues/22 -[try-eval-norm]: https://github.com/rust-lang/rust/blob/2627e9f3012a97d3136b3e11bf6bd0853c38a534/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs#L523-L537 -[#122687]: https://github.com/rust-lang/rust/pull/122687 - -## `AliasRelate` and structural normalization - -We structurally normalize an alias by applying one-step normalization until -we end up with a rigid alias, ambiguity, or overflow. This is done by repeatedly -evaluating `NormalizesTo` goals inside of a snapshot: [source][structural_norm]. - -`AliasRelate(lhs, rhs)` is implemented by first structurally normalizing both the -`lhs` and the `rhs` and then relating the resulting rigid types (or inference -variables). Importantly, if `lhs` or `rhs` ends up as an alias, this alias can -now be treated as rigid and gets unified without emitting a nested `AliasRelate` -goal: [source][structural-relate]. - -This means that `AliasRelate` with an unconstrained `rhs` ends up functioning -similar to `NormalizesTo`, acting as a function which fully normalizes `lhs` -before assigning the resulting rigid type to an inference variable. This is used by -`fn structurally_normalize_ty` both [inside] and [outside] of the trait solver. -This has to be used whenever we match on the value of some type, both inside -and outside of the trait solver. - - - -[structural_norm]: https://github.com/rust-lang/rust/blob/2627e9f3012a97d3136b3e11bf6bd0853c38a534/compiler/rustc_trait_selection/src/solve/alias_relate.rs#L140-L175 -[structural-relate]: https://github.com/rust-lang/rust/blob/a0569fa8f91b5271e92d2f73fd252de7d3d05b9c/compiler/rustc_trait_selection/src/solve/alias_relate.rs#L88-L107 -[inside]: https://github.com/rust-lang/rust/blob/a0569fa8f91b5271e92d2f73fd252de7d3d05b9c/compiler/rustc_trait_selection/src/solve/mod.rs#L278-L299 -[outside]: https://github.com/rust-lang/rust/blob/a0569fa8f91b5271e92d2f73fd252de7d3d05b9c/compiler/rustc_trait_selection/src/traits/structural_normalize.rs#L17-L48 - -## Deep normalization - -By walking over a type, and using `fn structurally_normalize_ty` for each encountered -alias, it is possible to deeply normalize a type, normalizing all aliases as much as -possible. However, this only works for aliases referencing bound variables if they are -not ambiguous as we're unable to replace the alias with a corresponding inference -variable without leaking universes. - - - -[generalize-no-alias]: https://github.com/rust-lang/rust/blob/a0569fa8f91b5271e92d2f73fd252de7d3d05b9c/compiler/rustc_infer/src/infer/relate/generalize.rs#L353-L358 - -## Outside of the trait solver - -The core type system - relating types and trait solving - will not need deep -normalization with the new solver. There are still some areas which depend on it. -For these areas there is the function `At::deeply_normalize`. Without additional -trait solver support deep normalization does not always work in case of ambiguity. -Luckily deep normalization is currently only necessary in places where there is no ambiguity. -`At::deeply_normalize` immediately fails if there's ambiguity. - -If we only care about the outermost layer of types, we instead use -`At::structurally_normalize` or `FnCtxt::(try_)structurally_resolve_type`. -Unlike `At::deeply_normalize`, structural normalization is also used in cases where we -have to handle ambiguity. - -Because this may result in behavior changes depending on how the trait solver handles -ambiguity, it is safer to also require full normalization there. This happens in -`FnCtxt::structurally_resolve_type` which always emits a hard error if the self type ends -up as an inference variable. There are some existing places which have a fallback for -inference variables instead. These places use `try_structurally_resolve_type` instead. - -## Why deep normalization with ambiguity is hard - -Fully correct deep normalization is very challenging, especially with the new solver -given that we do not want to deeply normalize inside of the solver. Mostly deeply normalizing -but sometimes failing to do so is bound to cause very hard to minimize and understand bugs. -If possible, avoiding any reliance on deep normalization entirely therefore feels preferable. - -If the solver itself does not deeply normalize, any inference constraints returned by the -solver would require normalization. Handling this correctly is ugly. This also means that -we change goals we provide to the trait solver by "normalizing away" some projections. - -The way we (mostly) guarantee deep normalization with the old solver is by eagerly replacing -the projection with an inference variable and emitting a nested `Projection` goal. This works -as `Projection` goals in the old solver deeply normalize. Unless we add another `PredicateKind` -for deep normalization to the new solver we cannot emulate this behavior. This does not work -for projections with bound variables, sometimes leaving them unnormalized. An approach which -also supports projections with bound variables will be even more involved. - -[^opaques]: opaque types are currently handled a bit differently. this may change in the future diff --git a/src/doc/rustc-dev-guide/src/solve/significant-changes.md b/src/doc/rustc-dev-guide/src/solve/significant-changes.md index c82b5d468961a..eac8f0318fb19 100644 --- a/src/doc/rustc-dev-guide/src/solve/significant-changes.md +++ b/src/doc/rustc-dev-guide/src/solve/significant-changes.md @@ -106,4 +106,4 @@ their ambiguous nested goals are returned to the caller which then evaluates the See [#122687] for more details. [#122687]: https://github.com/rust-lang/rust/pull/122687 -[normalization]: ./normalization.md +[normalization]: ../normalization.md From 7f1ae9b8c093041ac0584080d8a836189c89bcb1 Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 29 Apr 2025 19:35:26 +0100 Subject: [PATCH 19/27] Fix footnotes --- src/doc/rustc-dev-guide/src/normalization.md | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/normalization.md b/src/doc/rustc-dev-guide/src/normalization.md index 43ee05b8b57ea..ef530ccc5ed95 100644 --- a/src/doc/rustc-dev-guide/src/normalization.md +++ b/src/doc/rustc-dev-guide/src/normalization.md @@ -129,7 +129,7 @@ This is likely to change as const generics functionality is improved, for exampl There are two forms of normalization, structural (sometimes called *shallow*) and deep. Structural normalization should be thought of as only normalizing the "outermost" part of a type. On the other hand deep normalization will normalize *all* aliases in a type. -In practice structural normalization can result in more than just the outer layer of the type being normalized[^1], but this behaviour should not be relied upon. Unnormalizable non-rigid aliases making use of bound variables (`for<'a>`) cannot be normalized by either kind of normalization. +In practice structural normalization can result in more than just the outer layer of the type being normalized, but this behaviour should not be relied upon. Unnormalizable non-rigid aliases making use of bound variables (`for<'a>`) cannot be normalized by either kind of normalization. As an example: conceptually, structurally normalizing the type `Vec<::Assoc>` would be a no-op, whereas deeply normalizing would give `Vec`. In practice even structural normalization would give `Vec`, though, again, this should not be relied upon. @@ -162,8 +162,6 @@ In this example: - Normalizing `::Item` results in some new inference variable `?y`, as `::Item` is an ambiguous alias - The final result is that normalizing `Foo` results in `?y` -[^1]: In the new solver this is done implicitly - ## How to normalize When interfacing with the type system it will often be the case that it's necessary to request a type be normalized. There are a number of different entry points to the underlying normalization logic and each entry point should only be used in specific parts of the compiler. @@ -198,11 +196,7 @@ There are two ways to deeply normalize with an `InferCtxt`, `normalize` and `dee When the new solver is stabilized the `infcx.at.normalize` function will be removed and everything will have been migrated to the new deep or structural normalization methods. For this reason the `normalize` function is a no-op under the new solver, making it suitable only when the old solver needs normalization but the new solver does not. -Using `deeply_normalize` will result in errors being emitted when encountering ambiguous aliases[^1] as it is not possible to support normalizing *all* ambiguous aliases to inference variables[^2]. `deeply_normalize` should generally only be used in cases where we do not expect to encounter ambiguous aliases, for example when working with types from item signatures. - -[^1]: There is a subtle difference in how ambiguous aliases in binders are handled between old and new solver. In the old solver we fail to error on some ambiguous aliases inside of higher ranked types whereas the new solver correctly errors. - -[^2]: Ambiguous aliases inside of binders cannot be normalized to inference variables, this will be covered more later. +Using `deeply_normalize` will result in errors being emitted when encountering ambiguous aliases[^2] as it is not possible to support normalizing *all* ambiguous aliases to inference variables[^3]. `deeply_normalize` should generally only be used in cases where we do not expect to encounter ambiguous aliases, for example when working with types from item signatures. ##### `infcx.query_normalize` @@ -281,7 +275,7 @@ Ultimately this means that it is not always possible to ensure all aliases insid Diverging aliases, like ambiguous aliases, are normalized to inference variables. As normalizing diverging aliases results in trait solver cycles, it always results in an error in the old solver. In the new solver it only results in an error if we wind up requiring all goals to hold in the current context. E.g. normalizing diverging aliases during HIR typeck will result in an error in both solvers. -Alias well formedness doesn't require that the alias doesn't diverge[^1], this means that checking an alias is well formed isn't sufficient to cause an error to be emitted for diverging aliases; actually attempting to normalize the alias is required. +Alias well formedness doesn't require that the alias doesn't diverge[^4], this means that checking an alias is well formed isn't sufficient to cause an error to be emitted for diverging aliases; actually attempting to normalize the alias is required. Erroring on diverging aliases being a side effect of normalization means that it is very *arbitrary* whether we actually emit an error, it also differs between the old and new solver as we now normalize in less places. @@ -300,10 +294,16 @@ struct Bar::Diverges>(Box); In this example a diverging alias is used but we happen to not emit an error as we never explicitly normalize the defaults of generic parameters. If the `?Sized` opt out is removed then an error is emitted because we wind up happening to normalize a `::Diverges: Sized` goal which as a side effect results in erroring about the diverging alias. -Const aliases differ from type aliases a bit here; well formedness of const aliases requires that they can be successfully evaluated (via [`ConstEvaluatable`][const_evaluatable] goals). This means that simply checking well formedness of const arguments is sufficient to error if they would fail to evaluate. It is somewhat unclear whether it would make sense to adopt this for type aliases too or if const aliases should stop requiring this for well formedness[^2]. +Const aliases differ from type aliases a bit here; well formedness of const aliases requires that they can be successfully evaluated (via [`ConstEvaluatable`][const_evaluatable] goals). This means that simply checking well formedness of const arguments is sufficient to error if they would fail to evaluate. It is somewhat unclear whether it would make sense to adopt this for type aliases too or if const aliases should stop requiring this for well formedness[^5]. + +[^1]: In the new solver this is done implicitly + +[^2]: There is a subtle difference in how ambiguous aliases in binders are handled between old and new solver. In the old solver we fail to error on some ambiguous aliases inside of higher ranked types whereas the new solver correctly errors. + +[^3]: Ambiguous aliases inside of binders cannot be normalized to inference variables, this will be covered more later. -[^1]: As checking aliases are non-diverging cannot be done until they are fully concrete, this would either imply that we cant check aliases are well formed before codegen/const-evaluation or that aliases would go from being well-formed to not well-formed after monomorphization. +[^4]: As checking aliases are non-diverging cannot be done until they are fully concrete, this would either imply that we cant check aliases are well formed before codegen/const-evaluation or that aliases would go from being well-formed to not well-formed after monomorphization. -[^2]: Const aliases certainly wouldn't be *less* sound than type aliases if we stopped doing this +[^5]: Const aliases certainly wouldn't be *less* sound than type aliases if we stopped doing this [const_evaluatable]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/type.ClauseKind.html#variant.ConstEvaluatable \ No newline at end of file From 48bbf5a91baeb5f1493ec00c7941bf08566ceb47 Mon Sep 17 00:00:00 2001 From: Tshepang Mbambo Date: Tue, 29 Apr 2025 23:39:06 +0200 Subject: [PATCH 20/27] for a more friendly output Also, these are normal Rust things (crates/packages), so remove the word *normal*. --- src/doc/rustc-dev-guide/src/compiler-src.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/compiler-src.md b/src/doc/rustc-dev-guide/src/compiler-src.md index 0d3cbebb4da09..00aa96226849d 100644 --- a/src/doc/rustc-dev-guide/src/compiler-src.md +++ b/src/doc/rustc-dev-guide/src/compiler-src.md @@ -86,8 +86,12 @@ something like this: [`Span`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html [main]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver/fn.main.html -You can see the exact dependencies by reading the [`Cargo.toml`] for the various -crates, just like a normal Rust crate. +You can see the exact dependencies by running `cargo tree`, +just like you would for any other Rust package: + +```console +cargo tree --package rustc_driver +``` One final thing: [`src/llvm-project`] is a submodule for our fork of LLVM. During bootstrapping, LLVM is built and the [`compiler/rustc_llvm`] crate From 482ad5c51e5cab94b43f823f3cc2f94c87553237 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Thu, 1 May 2025 08:34:22 +0800 Subject: [PATCH 21/27] Remove redundant min-llvm-version annotations for LoongArch tests --- tests/ui/abi/compatibility.rs | 1 - .../bad-reg.loongarch64_lp64d.stderr | 12 +++++------ .../bad-reg.loongarch64_lp64s.stderr | 20 +++++++++---------- tests/ui/asm/loongarch/bad-reg.rs | 1 - 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/tests/ui/abi/compatibility.rs b/tests/ui/abi/compatibility.rs index be649029c8630..68706f1e821ac 100644 --- a/tests/ui/abi/compatibility.rs +++ b/tests/ui/abi/compatibility.rs @@ -40,7 +40,6 @@ //@ revisions: loongarch64 //@[loongarch64] compile-flags: --target loongarch64-unknown-linux-gnu //@[loongarch64] needs-llvm-components: loongarch -//@[loongarch64] min-llvm-version: 20 //FIXME: wasm is disabled due to . //FIXME @ revisions: wasm //FIXME @[wasm] compile-flags: --target wasm32-unknown-unknown diff --git a/tests/ui/asm/loongarch/bad-reg.loongarch64_lp64d.stderr b/tests/ui/asm/loongarch/bad-reg.loongarch64_lp64d.stderr index c88f3af764266..0e54411965012 100644 --- a/tests/ui/asm/loongarch/bad-reg.loongarch64_lp64d.stderr +++ b/tests/ui/asm/loongarch/bad-reg.loongarch64_lp64d.stderr @@ -1,35 +1,35 @@ error: invalid register `$r0`: constant zero cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:23:18 + --> $DIR/bad-reg.rs:22:18 | LL | asm!("", out("$r0") _); | ^^^^^^^^^^^^ error: invalid register `$tp`: reserved for TLS - --> $DIR/bad-reg.rs:25:18 + --> $DIR/bad-reg.rs:24:18 | LL | asm!("", out("$tp") _); | ^^^^^^^^^^^^ error: invalid register `$sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:27:18 + --> $DIR/bad-reg.rs:26:18 | LL | asm!("", out("$sp") _); | ^^^^^^^^^^^^ error: invalid register `$r21`: reserved by the ABI - --> $DIR/bad-reg.rs:29:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", out("$r21") _); | ^^^^^^^^^^^^^ error: invalid register `$fp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:31:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", out("$fp") _); | ^^^^^^^^^^^^ error: invalid register `$r31`: $r31 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:33:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", out("$r31") _); | ^^^^^^^^^^^^^ diff --git a/tests/ui/asm/loongarch/bad-reg.loongarch64_lp64s.stderr b/tests/ui/asm/loongarch/bad-reg.loongarch64_lp64s.stderr index cb8e55a9722db..6d0410dc6a13f 100644 --- a/tests/ui/asm/loongarch/bad-reg.loongarch64_lp64s.stderr +++ b/tests/ui/asm/loongarch/bad-reg.loongarch64_lp64s.stderr @@ -1,59 +1,59 @@ error: invalid register `$r0`: constant zero cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:23:18 + --> $DIR/bad-reg.rs:22:18 | LL | asm!("", out("$r0") _); | ^^^^^^^^^^^^ error: invalid register `$tp`: reserved for TLS - --> $DIR/bad-reg.rs:25:18 + --> $DIR/bad-reg.rs:24:18 | LL | asm!("", out("$tp") _); | ^^^^^^^^^^^^ error: invalid register `$sp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:27:18 + --> $DIR/bad-reg.rs:26:18 | LL | asm!("", out("$sp") _); | ^^^^^^^^^^^^ error: invalid register `$r21`: reserved by the ABI - --> $DIR/bad-reg.rs:29:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", out("$r21") _); | ^^^^^^^^^^^^^ error: invalid register `$fp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:31:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", out("$fp") _); | ^^^^^^^^^^^^ error: invalid register `$r31`: $r31 is used internally by LLVM and cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:33:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", out("$r31") _); | ^^^^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:37:26 + --> $DIR/bad-reg.rs:36:26 | LL | asm!("/* {} */", in(freg) f); | ^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:39:26 + --> $DIR/bad-reg.rs:38:26 | LL | asm!("/* {} */", out(freg) _); | ^^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:41:26 + --> $DIR/bad-reg.rs:40:26 | LL | asm!("/* {} */", in(freg) d); | ^^^^^^^^^^ error: register class `freg` requires at least one of the following target features: d, f - --> $DIR/bad-reg.rs:43:26 + --> $DIR/bad-reg.rs:42:26 | LL | asm!("/* {} */", out(freg) d); | ^^^^^^^^^^^ diff --git a/tests/ui/asm/loongarch/bad-reg.rs b/tests/ui/asm/loongarch/bad-reg.rs index db1c778e5a25c..685b460bc922c 100644 --- a/tests/ui/asm/loongarch/bad-reg.rs +++ b/tests/ui/asm/loongarch/bad-reg.rs @@ -1,7 +1,6 @@ //@ add-core-stubs //@ needs-asm-support //@ revisions: loongarch64_lp64d loongarch64_lp64s -//@ min-llvm-version: 20 //@[loongarch64_lp64d] compile-flags: --target loongarch64-unknown-linux-gnu //@[loongarch64_lp64d] needs-llvm-components: loongarch //@[loongarch64_lp64s] compile-flags: --target loongarch64-unknown-none-softfloat From 3e0cbbb8999906722f0c6dd65c65235d437277a1 Mon Sep 17 00:00:00 2001 From: "Martin Ombura Jr." <8682597+martinomburajr@users.noreply.github.com> Date: Thu, 1 May 2025 04:01:42 +0000 Subject: [PATCH 22/27] adds 'with' to help clarify how to build a new compiler --- src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md | 2 +- .../src/building/bootstrapping/what-bootstrapping-does.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md b/src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md index f72918c8377fc..bb7dd8dd46341 100644 --- a/src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md +++ b/src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md @@ -7,7 +7,7 @@ of the same compiler. This raises a chicken-and-egg paradox: where did the first compiler come from? It must have been written in a different language. In Rust's case it was [written in OCaml][ocaml-compiler]. However it was abandoned long ago and the -only way to build a modern version of rustc is a slightly less modern +only way to build a modern version of rustc is with a slightly less modern version. This is exactly how `x.py` works: it downloads the current beta release of diff --git a/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md b/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md index ffcfe25962572..ac1fa51e3d99a 100644 --- a/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md +++ b/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md @@ -9,7 +9,7 @@ the same compiler. This raises a chicken-and-egg paradox: where did the first compiler come from? It must have been written in a different language. In Rust's case it was [written in OCaml][ocaml-compiler]. However it was abandoned long ago and the -only way to build a modern version of `rustc` is a slightly less modern version. +only way to build a modern version of `rustc` is with a slightly less modern version. This is exactly how [`./x.py`] works: it downloads the current beta release of `rustc`, then uses it to compile the new compiler. From 27eb27423382738ae8f6d3d40a96396c7c541a37 Mon Sep 17 00:00:00 2001 From: The rustc-dev-guide Cronjob Bot Date: Thu, 1 May 2025 04:05:40 +0000 Subject: [PATCH 23/27] Preparing for merge from rustc --- src/doc/rustc-dev-guide/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/rustc-dev-guide/rust-version b/src/doc/rustc-dev-guide/rust-version index 67fa25f22288f..66b4fe2bf3bf0 100644 --- a/src/doc/rustc-dev-guide/rust-version +++ b/src/doc/rustc-dev-guide/rust-version @@ -1 +1 @@ -deb947971c8748f5c6203548ce4af9022f21eaf0 +0c33fe2c3d3eecadd17a84b110bb067288a64f1c From 9a3a212dae6f23d5abd4d0f9ab474b74556a278e Mon Sep 17 00:00:00 2001 From: "Martin Ombura Jr." <8682597+martinomburajr@users.noreply.github.com> Date: Thu, 1 May 2025 04:07:27 +0000 Subject: [PATCH 24/27] adds commas --- src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md | 2 +- .../src/building/bootstrapping/what-bootstrapping-does.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md b/src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md index bb7dd8dd46341..7f53097824cc9 100644 --- a/src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md +++ b/src/doc/rustc-dev-guide/src/building/bootstrapping/intro.md @@ -6,7 +6,7 @@ of the same compiler. This raises a chicken-and-egg paradox: where did the first compiler come from? It must have been written in a different language. In Rust's case it was -[written in OCaml][ocaml-compiler]. However it was abandoned long ago and the +[written in OCaml][ocaml-compiler]. However, it was abandoned long ago, and the only way to build a modern version of rustc is with a slightly less modern version. diff --git a/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md b/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md index ac1fa51e3d99a..a2930b3e42723 100644 --- a/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md +++ b/src/doc/rustc-dev-guide/src/building/bootstrapping/what-bootstrapping-does.md @@ -8,7 +8,7 @@ the same compiler. This raises a chicken-and-egg paradox: where did the first compiler come from? It must have been written in a different language. In Rust's case it was -[written in OCaml][ocaml-compiler]. However it was abandoned long ago and the +[written in OCaml][ocaml-compiler]. However, it was abandoned long ago, and the only way to build a modern version of `rustc` is with a slightly less modern version. This is exactly how [`./x.py`] works: it downloads the current beta release of From 714ea10ea41e97310a1b3d90fed4cfb3e2dd6b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Tue, 29 Apr 2025 02:11:41 +0200 Subject: [PATCH 25/27] rustdoc: Fix doctest heuristic for main fn wrapping --- src/librustdoc/doctest/make.rs | 65 ++++++++----------- tests/rustdoc-ui/doctest/auxiliary/items.rs | 1 + .../doctest/auxiliary/macro-after-main.rs | 1 - tests/rustdoc-ui/doctest/macro-after-main.rs | 16 ----- .../doctest/macro-after-main.stdout | 6 -- .../main-alongside-macro-calls.fail.stdout | 60 +++++++++++++++++ .../main-alongside-macro-calls.pass.stdout | 9 +++ .../doctest/main-alongside-macro-calls.rs | 44 +++++++++++++ .../doctest/main-alongside-stmts.rs | 33 ++++++++++ .../doctest/main-alongside-stmts.stdout | 7 ++ .../doctest/test-main-alongside-exprs.rs | 22 ------- .../doctest/test-main-alongside-exprs.stdout | 6 -- 12 files changed, 182 insertions(+), 88 deletions(-) create mode 100644 tests/rustdoc-ui/doctest/auxiliary/items.rs delete mode 100644 tests/rustdoc-ui/doctest/auxiliary/macro-after-main.rs delete mode 100644 tests/rustdoc-ui/doctest/macro-after-main.rs delete mode 100644 tests/rustdoc-ui/doctest/macro-after-main.stdout create mode 100644 tests/rustdoc-ui/doctest/main-alongside-macro-calls.fail.stdout create mode 100644 tests/rustdoc-ui/doctest/main-alongside-macro-calls.pass.stdout create mode 100644 tests/rustdoc-ui/doctest/main-alongside-macro-calls.rs create mode 100644 tests/rustdoc-ui/doctest/main-alongside-stmts.rs create mode 100644 tests/rustdoc-ui/doctest/main-alongside-stmts.stdout delete mode 100644 tests/rustdoc-ui/doctest/test-main-alongside-exprs.rs delete mode 100644 tests/rustdoc-ui/doctest/test-main-alongside-exprs.stdout diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index 4194abc8d5742..d4fbfb12582e7 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -301,8 +301,6 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result) -> Result) -> Result) -> bool { let mut is_extern_crate = false; if !info.has_global_allocator @@ -351,8 +347,6 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { - // We only push if it's the top item because otherwise, we would duplicate - // its content since the top-level item was already added. if fn_item.ident.name == sym::main { info.has_main_fn = true; } @@ -412,44 +406,41 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result { - is_extern_crate = check_item(&item, &mut info, crate_name); - } - StmtKind::Expr(ref expr) => { - if matches!(expr.kind, ast::ExprKind::Err(_)) { - reset_error_count(&psess); - return Err(()); - } - has_non_items = true; + is_extern_crate = check_item(item, &mut info, crate_name); } // We assume that the macro calls will expand to item(s) even though they could - // expand to statements and expressions. And the simple fact that we're trying - // to retrieve a `main` function inside it is a terrible idea. + // expand to statements and expressions. StmtKind::MacCall(ref mac_call) => { - if info.has_main_fn { - continue; - } - let mut iter = mac_call.mac.args.tokens.iter(); - - while let Some(token) = iter.next() { - if let TokenTree::Token(token, _) = token - && let TokenKind::Ident(name, _) = token.kind - && name == kw::Fn - && let Some(TokenTree::Token(fn_token, _)) = iter.peek() - && let TokenKind::Ident(fn_name, _) = fn_token.kind - && fn_name == sym::main - && let Some(TokenTree::Delimited(_, _, Delimiter::Parenthesis, _)) = { - iter.next(); - iter.peek() + if !info.has_main_fn { + // For backward compatibility, we look for the token sequence `fn main(…)` + // in the macro input (!) to crudely detect main functions "masked by a + // wrapper macro". For the record, this is a horrible heuristic! + // See . + let mut iter = mac_call.mac.args.tokens.iter(); + while let Some(token) = iter.next() { + if let TokenTree::Token(token, _) = token + && let TokenKind::Ident(kw::Fn, _) = token.kind + && let Some(TokenTree::Token(ident, _)) = iter.peek() + && let TokenKind::Ident(sym::main, _) = ident.kind + && let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, _)) = { + iter.next(); + iter.peek() + } + { + info.has_main_fn = true; + break; } - { - info.has_main_fn = true; - break; } } } - _ => { + StmtKind::Expr(ref expr) => { + if matches!(expr.kind, ast::ExprKind::Err(_)) { + reset_error_count(&psess); + return Err(()); + } has_non_items = true; } + StmtKind::Let(_) | StmtKind::Semi(_) | StmtKind::Empty => has_non_items = true, } // Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to diff --git a/tests/rustdoc-ui/doctest/auxiliary/items.rs b/tests/rustdoc-ui/doctest/auxiliary/items.rs new file mode 100644 index 0000000000000..40d4eb261e5a0 --- /dev/null +++ b/tests/rustdoc-ui/doctest/auxiliary/items.rs @@ -0,0 +1 @@ +fn item() {} diff --git a/tests/rustdoc-ui/doctest/auxiliary/macro-after-main.rs b/tests/rustdoc-ui/doctest/auxiliary/macro-after-main.rs deleted file mode 100644 index ed7584b742533..0000000000000 --- a/tests/rustdoc-ui/doctest/auxiliary/macro-after-main.rs +++ /dev/null @@ -1 +0,0 @@ -use std::string::String; diff --git a/tests/rustdoc-ui/doctest/macro-after-main.rs b/tests/rustdoc-ui/doctest/macro-after-main.rs deleted file mode 100644 index 0a42343f1c272..0000000000000 --- a/tests/rustdoc-ui/doctest/macro-after-main.rs +++ /dev/null @@ -1,16 +0,0 @@ -// This test checks a corner case where the macro calls used to be skipped, -// making them considered as statement, and therefore some cases where -// `include!` macro was then put into a function body, making the doctest -// compilation fail. - -//@ compile-flags:--test -//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" -//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" -//@ check-pass - -//! ``` -//! include!("./auxiliary/macro-after-main.rs"); -//! -//! fn main() {} -//! eprintln!(); -//! ``` diff --git a/tests/rustdoc-ui/doctest/macro-after-main.stdout b/tests/rustdoc-ui/doctest/macro-after-main.stdout deleted file mode 100644 index 72ffe2b5a27c5..0000000000000 --- a/tests/rustdoc-ui/doctest/macro-after-main.stdout +++ /dev/null @@ -1,6 +0,0 @@ - -running 1 test -test $DIR/macro-after-main.rs - (line 11) ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME - diff --git a/tests/rustdoc-ui/doctest/main-alongside-macro-calls.fail.stdout b/tests/rustdoc-ui/doctest/main-alongside-macro-calls.fail.stdout new file mode 100644 index 0000000000000..65989a8ef47c7 --- /dev/null +++ b/tests/rustdoc-ui/doctest/main-alongside-macro-calls.fail.stdout @@ -0,0 +1,60 @@ + +running 4 tests +test $DIR/main-alongside-macro-calls.rs - (line 19) ... ok +test $DIR/main-alongside-macro-calls.rs - (line 24) ... ok +test $DIR/main-alongside-macro-calls.rs - (line 28) ... FAILED +test $DIR/main-alongside-macro-calls.rs - (line 33) ... FAILED + +failures: + +---- $DIR/main-alongside-macro-calls.rs - (line 28) stdout ---- +error: macros that expand to items must be delimited with braces or followed by a semicolon + --> $DIR/main-alongside-macro-calls.rs:30:1 + | +LL | println!(); + | ^^^^^^^^^^ + | + = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: macro expansion ignores `{` and any tokens following + --> $SRC_DIR/std/src/macros.rs:LL:COL + | + ::: $DIR/main-alongside-macro-calls.rs:30:1 + | +LL | println!(); + | ---------- caused by the macro expansion here + | + = note: the usage of `print!` is likely invalid in item context + +error: aborting due to 2 previous errors + +Couldn't compile the test. +---- $DIR/main-alongside-macro-calls.rs - (line 33) stdout ---- +error: macros that expand to items must be delimited with braces or followed by a semicolon + --> $DIR/main-alongside-macro-calls.rs:34:1 + | +LL | println!(); + | ^^^^^^^^^^ + | + = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: macro expansion ignores `{` and any tokens following + --> $SRC_DIR/std/src/macros.rs:LL:COL + | + ::: $DIR/main-alongside-macro-calls.rs:34:1 + | +LL | println!(); + | ---------- caused by the macro expansion here + | + = note: the usage of `print!` is likely invalid in item context + +error: aborting due to 2 previous errors + +Couldn't compile the test. + +failures: + $DIR/main-alongside-macro-calls.rs - (line 28) + $DIR/main-alongside-macro-calls.rs - (line 33) + +test result: FAILED. 2 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/main-alongside-macro-calls.pass.stdout b/tests/rustdoc-ui/doctest/main-alongside-macro-calls.pass.stdout new file mode 100644 index 0000000000000..93a4bbd87368d --- /dev/null +++ b/tests/rustdoc-ui/doctest/main-alongside-macro-calls.pass.stdout @@ -0,0 +1,9 @@ + +running 4 tests +test $DIR/main-alongside-macro-calls.rs - (line 19) ... ok +test $DIR/main-alongside-macro-calls.rs - (line 24) ... ok +test $DIR/main-alongside-macro-calls.rs - (line 28) - compile fail ... ok +test $DIR/main-alongside-macro-calls.rs - (line 33) - compile fail ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/main-alongside-macro-calls.rs b/tests/rustdoc-ui/doctest/main-alongside-macro-calls.rs new file mode 100644 index 0000000000000..b455d8b0cc356 --- /dev/null +++ b/tests/rustdoc-ui/doctest/main-alongside-macro-calls.rs @@ -0,0 +1,44 @@ +// This test ensures that if there is are any macro calls alongside a `main` function, +// it will indeed consider the `main` function as the program entry point and *won't* +// generate its own `main` function to wrap everything even though macro calls are +// valid in statement contexts, too, and could just as well expand to statements or +// expressions (we don't perform any macro expansion to find `main`, see also +// ). +// +// See <./main-alongside-stmts.rs> for comparison. +// +//@ compile-flags:--test --test-args --test-threads=1 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ revisions: pass fail +//@[pass] check-pass +//@[fail] failure-status: 101 + +// Regression test for : + +//! ``` +//! fn main() {} +//! include!("./auxiliary/items.rs"); +//! ``` +//! +//! ``` +//! include!("./auxiliary/items.rs"); +//! fn main() {} +//! ``` + +// Regression test for : +// We test the "same" thing twice: Once via `compile_fail` to more closely mirror the reported +// regression and once without it to make sure that it leads to the expected rustc errors, +// namely `println!(…)` not being valid in item contexts. + +#![cfg_attr(pass, doc = " ```compile_fail")] +#![cfg_attr(fail, doc = " ```")] +//! fn main() {} +//! println!(); +//! ``` +//! +#![cfg_attr(pass, doc = " ```compile_fail")] +#![cfg_attr(fail, doc = " ```")] +//! println!(); +//! fn main() {} +//! ``` diff --git a/tests/rustdoc-ui/doctest/main-alongside-stmts.rs b/tests/rustdoc-ui/doctest/main-alongside-stmts.rs new file mode 100644 index 0000000000000..5965f928cdd13 --- /dev/null +++ b/tests/rustdoc-ui/doctest/main-alongside-stmts.rs @@ -0,0 +1,33 @@ +// This test ensures that if there is are any statements alongside a `main` function, +// it will not consider the `main` function as the program entry point but instead +// will generate its own `main` function to wrap everything as it needs to reside in a +// module where only *items* are permitted syntactically. +// +// See <./main-alongside-macro-calls.rs> for comparison. +// +// This is a regression test for: +// * +// * +// +//@ compile-flags:--test --test-args --test-threads=1 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ check-pass + +//! ``` +//! # if cfg!(miri) { return; } +//! use std::ops::Deref; +//! +//! fn main() { +//! assert!(false); +//! } +//! ``` +//! +//! ``` +//! let x = 2; +//! assert_eq!(x, 2); +//! +//! fn main() { +//! assert!(false); +//! } +//! ``` diff --git a/tests/rustdoc-ui/doctest/main-alongside-stmts.stdout b/tests/rustdoc-ui/doctest/main-alongside-stmts.stdout new file mode 100644 index 0000000000000..9b9a3fe8a68f7 --- /dev/null +++ b/tests/rustdoc-ui/doctest/main-alongside-stmts.stdout @@ -0,0 +1,7 @@ + +running 2 tests +test $DIR/main-alongside-stmts.rs - (line 17) ... ok +test $DIR/main-alongside-stmts.rs - (line 26) ... ok + +test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/test-main-alongside-exprs.rs b/tests/rustdoc-ui/doctest/test-main-alongside-exprs.rs deleted file mode 100644 index ee2299c0fd87e..0000000000000 --- a/tests/rustdoc-ui/doctest/test-main-alongside-exprs.rs +++ /dev/null @@ -1,22 +0,0 @@ -// This test ensures that if there is an expression alongside a `main` -// function, it will not consider the entire code to be part of the `main` -// function and will generate its own function to wrap everything. -// -// This is a regression test for: -// * -// * -//@ compile-flags:--test -//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" -//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" -//@ check-pass - -#![crate_name = "foo"] - -//! ``` -//! # if cfg!(miri) { return; } -//! use std::ops::Deref; -//! -//! fn main() { -//! println!("Hi!"); -//! } -//! ``` diff --git a/tests/rustdoc-ui/doctest/test-main-alongside-exprs.stdout b/tests/rustdoc-ui/doctest/test-main-alongside-exprs.stdout deleted file mode 100644 index 90d7c3546bf10..0000000000000 --- a/tests/rustdoc-ui/doctest/test-main-alongside-exprs.stdout +++ /dev/null @@ -1,6 +0,0 @@ - -running 1 test -test $DIR/test-main-alongside-exprs.rs - (line 15) ... ok - -test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME - From 5d308148aa06f011f062488e81a10aeeb338ba35 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 1 May 2025 16:19:24 +0200 Subject: [PATCH 26/27] allow `#[rustc_std_internal_symbol]` in combination with `#[naked]` --- compiler/rustc_passes/src/check_attr.rs | 1 + tests/ui/asm/naked-functions.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index a61d446a3a93b..f04b167889f19 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -625,6 +625,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { sym::naked, sym::instruction_set, sym::repr, + sym::rustc_std_internal_symbol, // code generation sym::cold, // documentation diff --git a/tests/ui/asm/naked-functions.rs b/tests/ui/asm/naked-functions.rs index 1eeb716e98a1f..cb5fde9a80b81 100644 --- a/tests/ui/asm/naked-functions.rs +++ b/tests/ui/asm/naked-functions.rs @@ -2,7 +2,7 @@ //@ ignore-nvptx64 //@ ignore-spirv -#![feature(asm_unwind, linkage)] +#![feature(asm_unwind, linkage, rustc_attrs)] #![crate_type = "lib"] use std::arch::{asm, naked_asm}; @@ -225,3 +225,9 @@ pub extern "C" fn compatible_doc_attributes() { pub extern "C" fn compatible_linkage() { naked_asm!("", options(raw)); } + +#[rustc_std_internal_symbol] +#[unsafe(naked)] +pub extern "C" fn rustc_std_internal_symbol() { + naked_asm!("", options(raw)); +} From bc68d3a14467ef6fcebb003f79c2f4c5ef5dd08e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 1 May 2025 17:42:13 +0200 Subject: [PATCH 27/27] Improve error output in case `nodejs` or `npm` is not installed for rustdoc-gui test suite --- src/tools/rustdoc-gui-test/src/config.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/tools/rustdoc-gui-test/src/config.rs b/src/tools/rustdoc-gui-test/src/config.rs index 950e2fa478dcf..b9d08a0a2950d 100644 --- a/src/tools/rustdoc-gui-test/src/config.rs +++ b/src/tools/rustdoc-gui-test/src/config.rs @@ -20,8 +20,8 @@ pub(crate) struct Config { impl Config { pub(crate) fn from_args(args: Vec) -> Self { let mut opts = Options::new(); - opts.reqopt("", "nodejs", "absolute path of nodejs", "PATH") - .reqopt("", "npm", "absolute path of npm", "PATH") + opts.optopt("", "nodejs", "absolute path of nodejs", "PATH") + .optopt("", "npm", "absolute path of npm", "PATH") .reqopt("", "out-dir", "output path of doc compilation", "PATH") .reqopt("", "rust-src", "root source of the rust source", "PATH") .reqopt( @@ -47,9 +47,18 @@ impl Config { Err(f) => panic!("{:?}", f), }; + let Some(nodejs) = matches.opt_str("nodejs").map(PathBuf::from) else { + eprintln!("`nodejs` was not provided. If not available, please install it"); + std::process::exit(1); + }; + let Some(npm) = matches.opt_str("npm").map(PathBuf::from) else { + eprintln!("`npm` was not provided. If not available, please install it"); + std::process::exit(1); + }; + Self { - nodejs: matches.opt_str("nodejs").map(PathBuf::from).expect("nodejs isn't available"), - npm: matches.opt_str("npm").map(PathBuf::from).expect("npm isn't available"), + nodejs, + npm, rust_src: matches.opt_str("rust-src").map(PathBuf::from).unwrap(), out_dir: matches.opt_str("out-dir").map(PathBuf::from).unwrap(), initial_cargo: matches.opt_str("initial-cargo").map(PathBuf::from).unwrap(),