From eb7cb897ea71f57eed0495f1fbc12b7cca0e55df Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 12 Apr 2026 13:26:38 +0200 Subject: [PATCH 01/31] implement our own `ulps` check to test `f16` the `float-cmp` and `num-traits` libraries don't (yet) support f16. Turns out we didn't really need much from them, just the ulps check. I've adapted the ulps check from miri instead --- Cargo.lock | 10 ---------- crates/std_float/tests/float.rs | 2 ++ crates/test_helpers/Cargo.toml | 1 - crates/test_helpers/src/approxeq.rs | 13 +++++++++---- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3b950bd5069c..80a0e1999cfaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,15 +71,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits", -] - [[package]] name = "futures-core" version = "0.3.32" @@ -388,7 +379,6 @@ dependencies = [ name = "test_helpers" version = "0.1.0" dependencies = [ - "float-cmp", "proptest", ] diff --git a/crates/std_float/tests/float.rs b/crates/std_float/tests/float.rs index 0fa5da3dca506..ece8cdda8f432 100644 --- a/crates/std_float/tests/float.rs +++ b/crates/std_float/tests/float.rs @@ -1,4 +1,5 @@ #![feature(portable_simd)] +#![feature(f16)] macro_rules! unary_test { { $scalar:tt, $($func:tt),+ } => { @@ -91,5 +92,6 @@ macro_rules! impl_tests { } } +impl_tests! { f16 } impl_tests! { f32 } impl_tests! { f64 } diff --git a/crates/test_helpers/Cargo.toml b/crates/test_helpers/Cargo.toml index da7ef7bd9945c..2abacd89b8e0b 100644 --- a/crates/test_helpers/Cargo.toml +++ b/crates/test_helpers/Cargo.toml @@ -6,4 +6,3 @@ publish = false [dependencies] proptest = { workspace = true, features = ["alloc", "std"] } -float-cmp = "0.10" diff --git a/crates/test_helpers/src/approxeq.rs b/crates/test_helpers/src/approxeq.rs index 57b43a16bc6fe..b868e09e1483a 100644 --- a/crates/test_helpers/src/approxeq.rs +++ b/crates/test_helpers/src/approxeq.rs @@ -1,7 +1,5 @@ //! Compare numeric types approximately. -use float_cmp::Ulps; - pub trait ApproxEq { fn approxeq(&self, other: &Self, _ulps: i64) -> bool; fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result; @@ -43,7 +41,14 @@ macro_rules! impl_float_approxeq { if self.is_nan() && other.is_nan() { true } else { - (self.ulps(other) as i64).abs() <= ulps + let allowed_ulp_diff = ulps; + + // Approximate the ULP by taking half the distance between the number one place "up" + // and the number one place "down". + let ulp = (other.next_up() - other.next_down()) / 2.0; + let ulp_diff = ((self - other) / ulp).abs().round() as i64; + + ulp_diff <= allowed_ulp_diff } } @@ -55,7 +60,7 @@ macro_rules! impl_float_approxeq { }; } -impl_float_approxeq! { f32, f64 } +impl_float_approxeq! { f16, f32, f64 } impl ApproxEq for [T; N] { fn approxeq(&self, other: &Self, ulps: i64) -> bool { From f40da91254c30f4379b6a6f9301c9930d352b992 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 16 Apr 2026 15:56:54 +0200 Subject: [PATCH 02/31] disable `f16` tests when not `target_has_reliable_f16_math` relevant for the cranelift backend, possibly other cases/targets too --- crates/core_simd/Cargo.toml | 8 ++++++++ crates/core_simd/tests/f16_ops.rs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/crates/core_simd/Cargo.toml b/crates/core_simd/Cargo.toml index 6e576084ecfba..05fc63a6a29d3 100644 --- a/crates/core_simd/Cargo.toml +++ b/crates/core_simd/Cargo.toml @@ -31,3 +31,11 @@ path = "../test_helpers" [dev-dependencies] std_float = { path = "../std_float/", features = ["as_crate"] } + +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + # Internal features aren't marked known config by default, we use these to + # gate tests. + 'cfg(target_has_reliable_f16_math)', +] diff --git a/crates/core_simd/tests/f16_ops.rs b/crates/core_simd/tests/f16_ops.rs index f89bdf4738f8b..03e239b745f12 100644 --- a/crates/core_simd/tests/f16_ops.rs +++ b/crates/core_simd/tests/f16_ops.rs @@ -1,5 +1,7 @@ #![feature(portable_simd)] #![feature(f16)] +#![expect(internal_features)] +#![feature(cfg_target_has_reliable_f16_f128)] #[macro_use] mod ops_macros; @@ -7,4 +9,5 @@ mod ops_macros; // FIXME: some f16 operations cause rustc to hang on wasm simd // https://github.com/llvm/llvm-project/issues/189251 #[cfg(not(all(target_arch = "wasm32", target_feature = "simd128")))] +#[cfg(target_has_reliable_f16_math)] impl_float_tests! { f16, i16 } From c4f3110f54c86ca5f5b08ddb050d3f517b450e6e Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Fri, 8 May 2026 00:12:43 -0400 Subject: [PATCH 03/31] Fix pointer API to match what was stabilized --- crates/core_simd/src/simd/ptr.rs | 46 +++++++++++++++++++++ crates/core_simd/src/simd/ptr/const_ptr.rs | 47 +++------------------- crates/core_simd/src/simd/ptr/mut_ptr.rs | 44 +++----------------- crates/core_simd/tests/pointers.rs | 24 +++++++++-- 4 files changed, 77 insertions(+), 84 deletions(-) diff --git a/crates/core_simd/src/simd/ptr.rs b/crates/core_simd/src/simd/ptr.rs index 3f8e666911853..a27367ceb791b 100644 --- a/crates/core_simd/src/simd/ptr.rs +++ b/crates/core_simd/src/simd/ptr.rs @@ -9,3 +9,49 @@ mod sealed { pub use const_ptr::*; pub use mut_ptr::*; + +use crate::simd::Simd; + +/// Creates pointers with the given addresses and no provenance. +/// +/// Equivalent to calling [`core::ptr::without_provenance`] on each element. +#[inline] +pub fn without_provenance(addr: Simd) -> Simd<*const T, N> { + // An int-to-pointer transmute currently has exactly the intended semantics: it creates a + // pointer without provenance. Note that this is *not* a stable guarantee about transmute + // semantics, it relies on sysroot crates having special status. + // SAFETY: every valid integer is also a valid pointer (as long as you don't dereference that + // pointer). + unsafe { core::mem::transmute_copy(&addr) } +} + +/// Creates mutable pointers with the given addresses and no provenance. +/// +/// Equivalent to calling [`core::ptr::without_provenance_mut`] on each element. +#[inline] +pub fn without_provenance_mut(addr: Simd) -> Simd<*mut T, N> { + // An int-to-pointer transmute currently has exactly the intended semantics: it creates a + // pointer without provenance. Note that this is *not* a stable guarantee about transmute + // semantics, it relies on sysroot crates having special status. + // SAFETY: every valid integer is also a valid pointer (as long as you don't dereference that + // pointer). + unsafe { core::mem::transmute_copy(&addr) } +} + +/// Converts addresses back to pointers, picking up some previously "exposed" provenance. +/// +/// Equivalent to calling [`core::ptr::with_exposed_provenance`] on each element. +#[inline] +pub fn with_exposed_provenance(addr: Simd) -> Simd<*const T, N> { + // SAFETY: addr is a vector of usize + unsafe { core::intrinsics::simd::simd_with_exposed_provenance(addr) } +} + +/// Converts addresses back to mutable pointers, picking up some previously "exposed" provenance. +/// +/// Equivalent to calling [`core::ptr::with_exposed_provenance_mut`] on each element. +#[inline] +pub fn with_exposed_provenance_mut(addr: Simd) -> Simd<*mut T, N> { + // SAFETY: addr is a vector of usize + unsafe { core::intrinsics::simd::simd_with_exposed_provenance(addr) } +} diff --git a/crates/core_simd/src/simd/ptr/const_ptr.rs b/crates/core_simd/src/simd/ptr/const_ptr.rs index 7ef9dc21373ec..be2b9e6b4835f 100644 --- a/crates/core_simd/src/simd/ptr/const_ptr.rs +++ b/crates/core_simd/src/simd/ptr/const_ptr.rs @@ -33,44 +33,21 @@ pub trait SimdConstPtr: Copy + Sealed { /// Gets the "address" portion of the pointer. /// - /// This method discards pointer semantic metadata, so the result cannot be - /// directly cast into a valid pointer. - /// - /// This method semantically discards *provenance* and - /// *address-space* information. To properly restore that information, use [`Self::with_addr`]. - /// /// Equivalent to calling [`pointer::addr`] on each element. fn addr(self) -> Self::Usize; - /// Converts an address to a pointer without giving it any provenance. - /// - /// Without provenance, this pointer is not associated with any actual allocation. Such a - /// no-provenance pointer may be used for zero-sized memory accesses (if suitably aligned), but - /// non-zero-sized memory accesses with a no-provenance pointer are UB. No-provenance pointers - /// are little more than a usize address in disguise. - /// - /// This is different from [`Self::with_exposed_provenance`], which creates a pointer that picks up a - /// previously exposed provenance. - /// - /// Equivalent to calling [`core::ptr::without_provenance`] on each element. - fn without_provenance(addr: Self::Usize) -> Self; - /// Creates a new pointer with the given address. /// - /// This performs the same operation as a cast, but copies the *address-space* and - /// *provenance* of `self` to the new pointer. - /// /// Equivalent to calling [`pointer::with_addr`] on each element. fn with_addr(self, addr: Self::Usize) -> Self; /// Exposes the "provenance" part of the pointer for future use in - /// [`Self::with_exposed_provenance`] and returns the "address" portion. - fn expose_provenance(self) -> Self::Usize; - - /// Converts an address back to a pointer, picking up a previously "exposed" provenance. + /// [`super::with_exposed_provenance`] and returns the "address" portion. + /// + /// Equivalent to calling [`pointer::expose_provenance`] on each element. /// - /// Equivalent to calling [`core::ptr::with_exposed_provenance`] on each element. - fn with_exposed_provenance(addr: Self::Usize) -> Self; + /// See [`super::with_exposed_provenance`] for the matching constructor. + fn expose_provenance(self) -> Self::Usize; /// Calculates the offset from a pointer using wrapping arithmetic. /// @@ -128,14 +105,6 @@ impl SimdConstPtr for Simd<*const T, N> { unsafe { core::mem::transmute_copy(&self) } } - #[inline] - fn without_provenance(addr: Self::Usize) -> Self { - // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. - // SAFETY: Integer-to-pointer transmutes are valid (if you are okay with not getting any - // provenance). - unsafe { core::mem::transmute_copy(&addr) } - } - #[inline] fn with_addr(self, addr: Self::Usize) -> Self { // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. @@ -154,12 +123,6 @@ impl SimdConstPtr for Simd<*const T, N> { unsafe { core::intrinsics::simd::simd_expose_provenance(self) } } - #[inline] - fn with_exposed_provenance(addr: Self::Usize) -> Self { - // Safety: `self` is a pointer vector - unsafe { core::intrinsics::simd::simd_with_exposed_provenance(addr) } - } - #[inline] fn wrapping_offset(self, count: Self::Isize) -> Self { // Safety: simd_arith_offset takes a vector of pointers and a vector of offsets diff --git a/crates/core_simd/src/simd/ptr/mut_ptr.rs b/crates/core_simd/src/simd/ptr/mut_ptr.rs index 3b9b75ddf5660..78b5bef4d9740 100644 --- a/crates/core_simd/src/simd/ptr/mut_ptr.rs +++ b/crates/core_simd/src/simd/ptr/mut_ptr.rs @@ -33,41 +33,21 @@ pub trait SimdMutPtr: Copy + Sealed { /// Gets the "address" portion of the pointer. /// - /// This method discards pointer semantic metadata, so the result cannot be - /// directly cast into a valid pointer. - /// /// Equivalent to calling [`pointer::addr`] on each element. fn addr(self) -> Self::Usize; - /// Converts an address to a pointer without giving it any provenance. - /// - /// Without provenance, this pointer is not associated with any actual allocation. Such a - /// no-provenance pointer may be used for zero-sized memory accesses (if suitably aligned), but - /// non-zero-sized memory accesses with a no-provenance pointer are UB. No-provenance pointers - /// are little more than a usize address in disguise. - /// - /// This is different from [`Self::with_exposed_provenance`], which creates a pointer that picks up a - /// previously exposed provenance. - /// - /// Equivalent to calling [`core::ptr::without_provenance`] on each element. - fn without_provenance(addr: Self::Usize) -> Self; - /// Creates a new pointer with the given address. /// - /// This performs the same operation as a cast, but copies the *address-space* and - /// *provenance* of `self` to the new pointer. - /// /// Equivalent to calling [`pointer::with_addr`] on each element. fn with_addr(self, addr: Self::Usize) -> Self; /// Exposes the "provenance" part of the pointer for future use in - /// [`Self::with_exposed_provenance`] and returns the "address" portion. - fn expose_provenance(self) -> Self::Usize; - - /// Converts an address back to a pointer, picking up a previously "exposed" provenance. + /// [`super::with_exposed_provenance_mut`] and returns the "address" portion. /// - /// Equivalent to calling [`core::ptr::with_exposed_provenance_mut`] on each element. - fn with_exposed_provenance(addr: Self::Usize) -> Self; + /// Equivalent to calling [`pointer::expose_provenance`] on each element. + /// + /// See [`super::with_exposed_provenance_mut`] for the matching constructor. + fn expose_provenance(self) -> Self::Usize; /// Calculates the offset from a pointer using wrapping arithmetic. /// @@ -125,14 +105,6 @@ impl SimdMutPtr for Simd<*mut T, N> { unsafe { core::mem::transmute_copy(&self) } } - #[inline] - fn without_provenance(addr: Self::Usize) -> Self { - // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. - // SAFETY: Integer-to-pointer transmutes are valid (if you are okay with not getting any - // provenance). - unsafe { core::mem::transmute_copy(&addr) } - } - #[inline] fn with_addr(self, addr: Self::Usize) -> Self { // FIXME(strict_provenance_magic): I am magic and should be a compiler intrinsic. @@ -151,12 +123,6 @@ impl SimdMutPtr for Simd<*mut T, N> { unsafe { core::intrinsics::simd::simd_expose_provenance(self) } } - #[inline] - fn with_exposed_provenance(addr: Self::Usize) -> Self { - // Safety: `self` is a pointer vector - unsafe { core::intrinsics::simd::simd_with_exposed_provenance(addr) } - } - #[inline] fn wrapping_offset(self, count: Self::Isize) -> Self { // Safety: simd_arith_offset takes a vector of pointers and a vector of offsets diff --git a/crates/core_simd/tests/pointers.rs b/crates/core_simd/tests/pointers.rs index 6e74c2d18b1ed..06670cc9dd976 100644 --- a/crates/core_simd/tests/pointers.rs +++ b/crates/core_simd/tests/pointers.rs @@ -2,7 +2,7 @@ use core_simd::simd::{ Simd, - ptr::{SimdConstPtr, SimdMutPtr}, + ptr::{self, SimdConstPtr, SimdMutPtr}, }; macro_rules! common_tests { @@ -82,11 +82,20 @@ mod const_ptr { fn with_exposed_provenance() { test_helpers::test_unary_elementwise( - &Simd::<*const u32, LANES>::with_exposed_provenance, + &ptr::with_exposed_provenance::, &core::ptr::with_exposed_provenance::, &|_| true, ); } + + fn without_provenance() { + test_helpers::test_unary_elementwise( + &ptr::without_provenance::, + &core::ptr::without_provenance::, + &|_| true, + ); + } + } } @@ -105,10 +114,19 @@ mod mut_ptr { fn with_exposed_provenance() { test_helpers::test_unary_elementwise( - &Simd::<*mut u32, LANES>::with_exposed_provenance, + &ptr::with_exposed_provenance_mut::, &core::ptr::with_exposed_provenance_mut::, &|_| true, ); } + + fn without_provenance() { + test_helpers::test_unary_elementwise( + &ptr::without_provenance_mut::, + &core::ptr::without_provenance_mut::, + &|_| true, + ); + } + } } From beafe835d5772f8cd3408fe80cf08cf79af5aaa4 Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Thu, 28 May 2026 20:41:46 +0100 Subject: [PATCH 04/31] Optimize `swizzle_dyn` for AArch64 with N > 16 We can do swizzles for 24, 32, 48 and 64 byte vectors by stacking multiple TBL instructions. See https://godbolt.org/z/PE95nrqjj for a comparison of the generated assembly. --- crates/core_simd/src/swizzle_dyn.rs | 86 +++++++++++++++++++++++------ crates/test_helpers/src/lib.rs | 2 +- 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/crates/core_simd/src/swizzle_dyn.rs b/crates/core_simd/src/swizzle_dyn.rs index ae0b174973da7..cf37aff9a76d1 100644 --- a/crates/core_simd/src/swizzle_dyn.rs +++ b/crates/core_simd/src/swizzle_dyn.rs @@ -13,11 +13,6 @@ impl Simd { #[inline] pub fn swizzle_dyn(self, idxs: Simd) -> Self { #![allow(unused_imports, unused_unsafe)] - #[cfg(all( - any(target_arch = "aarch64", target_arch = "arm64ec"), - target_endian = "little" - ))] - use core::arch::aarch64::{uint8x8_t, vqtbl1q_u8, vtbl1_u8}; #[cfg(all( target_arch = "arm", target_feature = "v7", @@ -37,25 +32,15 @@ impl Simd { unsafe { match N { #[cfg(all( - any( - target_arch = "aarch64", - target_arch = "arm64ec", - all(target_arch = "arm", target_feature = "v7") - ), + any(target_arch = "aarch64", target_arch = "arm64ec"), target_feature = "neon", target_endian = "little" ))] - 8 => transize(vtbl1_u8, self, idxs), + 8 | 16 | 24 | 32 | 48 | 64 => aarch64_swizzle(self, idxs), #[cfg(target_feature = "ssse3")] 16 => transize(x86::_mm_shuffle_epi8, self, zeroing_idxs(idxs)), #[cfg(target_feature = "simd128")] 16 => transize(wasm::i8x16_swizzle, self, idxs), - #[cfg(all( - any(target_arch = "aarch64", target_arch = "arm64ec"), - target_feature = "neon", - target_endian = "little" - ))] - 16 => transize(vqtbl1q_u8, self, idxs), #[cfg(all( target_arch = "arm", target_feature = "v7", @@ -126,6 +111,73 @@ unsafe fn armv7_neon_swizzle_u8x16(bytes: Simd, idxs: Simd) -> S } } +/// AArch64 NEON supports swizzling 8, 16, 24, 32, 48 or 64 by stacking multiple TBL instructions. +/// +/// # Safety +/// This requires AArch64 NEON to work +#[cfg(all( + any(target_arch = "aarch64", target_arch = "arm64ec"), + target_feature = "neon", + target_endian = "little" +))] +unsafe fn aarch64_swizzle(bytes: Simd, idxs: Simd) -> Simd { + use core::arch::aarch64::*; + use core::mem::transmute_copy; + + // SAFETY: Caller promised AArch64 NEON support + unsafe { + match N { + 8 => transmute_copy(&vtbl1_u8(transmute_copy(&bytes), transmute_copy(&idxs))), + 16 => transmute_copy(&vqtbl1q_u8(transmute_copy(&bytes), transmute_copy(&idxs))), + 24 => { + let bytes: uint8x8x3_t = transmute_copy(&bytes); + let idxs: uint8x8x3_t = transmute_copy(&idxs); + + let ret0 = vtbl3_u8(bytes, idxs.0); + let ret1 = vtbl3_u8(bytes, idxs.1); + let ret2 = vtbl3_u8(bytes, idxs.2); + + let ret = uint8x8x3_t(ret0, ret1, ret2); + transmute_copy(&ret) + } + 32 => { + let bytes: uint8x16x2_t = transmute_copy(&bytes); + let idxs: uint8x16x2_t = transmute_copy(&idxs); + + let ret0 = vqtbl2q_u8(bytes, idxs.0); + let ret1 = vqtbl2q_u8(bytes, idxs.1); + + let ret = uint8x16x2_t(ret0, ret1); + transmute_copy(&ret) + } + 48 => { + let bytes: uint8x16x3_t = transmute_copy(&bytes); + let idxs: uint8x16x3_t = transmute_copy(&idxs); + + let ret0 = vqtbl3q_u8(bytes, idxs.0); + let ret1 = vqtbl3q_u8(bytes, idxs.1); + let ret2 = vqtbl3q_u8(bytes, idxs.2); + + let ret = uint8x16x3_t(ret0, ret1, ret2); + transmute_copy(&ret) + } + 64 => { + let bytes: uint8x16x4_t = transmute_copy(&bytes); + let idxs: uint8x16x4_t = transmute_copy(&idxs); + + let ret0 = vqtbl4q_u8(bytes, idxs.0); + let ret1 = vqtbl4q_u8(bytes, idxs.1); + let ret2 = vqtbl4q_u8(bytes, idxs.2); + let ret3 = vqtbl4q_u8(bytes, idxs.3); + + let ret = uint8x16x4_t(ret0, ret1, ret2, ret3); + transmute_copy(&ret) + } + _ => unreachable!(), + } + } +} + /// "vpshufb like it was meant to be" on AVX2 /// /// # Safety diff --git a/crates/test_helpers/src/lib.rs b/crates/test_helpers/src/lib.rs index ce3680ac2c306..6661b0c53c7a9 100644 --- a/crates/test_helpers/src/lib.rs +++ b/crates/test_helpers/src/lib.rs @@ -678,7 +678,7 @@ macro_rules! test_lanes { //lanes_45 45; //lanes_46 46; lanes_47 47; - //lanes_48 48; + lanes_48 48; //lanes_49 49; //lanes_50 50; //lanes_51 51; From b16ee624234def39a75bc8441b2518a6854fa028 Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Sun, 31 May 2026 23:59:35 +0100 Subject: [PATCH 05/31] Provide `From` impls for AArch64 ACLE tuple types. `From` impls were already provided for the normal AArch64 ACLE vector types, but not for the x2,x3,x4 tuples of ACLE vectors. Rectify that. Also provide impls for vectors of `f16`, as I noticed they were missing. --- crates/core_simd/src/vendor/arm.rs | 50 ++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/crates/core_simd/src/vendor/arm.rs b/crates/core_simd/src/vendor/arm.rs index 3dc54481b6fd4..d2f4ffd8e867e 100644 --- a/crates/core_simd/src/vendor/arm.rs +++ b/crates/core_simd/src/vendor/arm.rs @@ -7,6 +7,16 @@ use core::arch::arm::*; #[cfg(any(target_arch = "aarch64", target_arch = "arm64ec"))] use core::arch::aarch64::*; +/// Transmute between `Simd` and ACLE tuple types. +macro_rules! tuple { + ($scalar:ty,$tuple:ty) => { + from_transmute! { unsafe Simd<$scalar, { size_of::<$tuple>() / size_of::<$scalar>() }> => $tuple } + }; + ($scalar:ty,$($tuples:ty),*) => { + $(tuple! { $scalar, $tuples })* + }; +} + #[cfg(all( any( target_arch = "aarch64", @@ -18,9 +28,6 @@ use core::arch::aarch64::*; mod neon { use super::*; - from_transmute! { unsafe f32x2 => float32x2_t } - from_transmute! { unsafe f32x4 => float32x4_t } - from_transmute! { unsafe u8x8 => uint8x8_t } from_transmute! { unsafe u8x16 => uint8x16_t } from_transmute! { unsafe i8x8 => int8x8_t } @@ -34,11 +41,15 @@ mod neon { from_transmute! { unsafe i16x8 => int16x8_t } from_transmute! { unsafe u16x4 => poly16x4_t } from_transmute! { unsafe u16x8 => poly16x8_t } + from_transmute! { unsafe f16x4 => float16x4_t } + from_transmute! { unsafe f16x8 => float16x8_t } from_transmute! { unsafe u32x2 => uint32x2_t } from_transmute! { unsafe u32x4 => uint32x4_t } from_transmute! { unsafe i32x2 => int32x2_t } from_transmute! { unsafe i32x4 => int32x4_t } + from_transmute! { unsafe f32x2 => float32x2_t } + from_transmute! { unsafe f32x4 => float32x4_t } from_transmute! { unsafe Simd => uint64x1_t } from_transmute! { unsafe u64x2 => uint64x2_t } @@ -46,6 +57,36 @@ mod neon { from_transmute! { unsafe i64x2 => int64x2_t } from_transmute! { unsafe Simd => poly64x1_t } from_transmute! { unsafe u64x2 => poly64x2_t } + + tuple!(i8, int8x8x2_t, int8x8x3_t, int8x8x4_t); + tuple!(i8, int8x16x2_t, int8x16x3_t, int8x16x4_t); + tuple!(u8, uint8x8x2_t, uint8x8x3_t, uint8x8x4_t); + tuple!(u8, uint8x16x2_t, uint8x16x3_t, uint8x16x4_t); + tuple!(u8, poly8x8x2_t, poly8x8x3_t, poly8x8x4_t); + tuple!(u8, poly8x16x2_t, poly8x16x3_t, poly8x16x4_t); + + tuple!(i16, int16x4x2_t, int16x4x3_t, int16x4x4_t); + tuple!(i16, int16x8x2_t, int16x8x3_t, int16x8x4_t); + tuple!(u16, uint16x4x2_t, uint16x4x3_t, uint16x4x4_t); + tuple!(u16, uint16x8x2_t, uint16x8x3_t, uint16x8x4_t); + tuple!(u16, poly16x4x2_t, poly16x4x3_t, poly16x4x4_t); + tuple!(u16, poly16x8x2_t, poly16x8x3_t, poly16x8x4_t); + tuple!(f16, float16x4x2_t, float16x4x3_t, float16x4x4_t); + tuple!(f16, float16x8x2_t, float16x8x3_t, float16x8x4_t); + + tuple!(i32, int32x2x2_t, int32x2x3_t, int32x2x4_t); + tuple!(i32, int32x4x2_t, int32x4x3_t, int32x4x4_t); + tuple!(u32, uint32x2x2_t, uint32x2x3_t, uint32x2x4_t); + tuple!(u32, uint32x4x2_t, uint32x4x3_t, uint32x4x4_t); + tuple!(f32, float32x2x2_t, float32x2x3_t, float32x2x4_t); + tuple!(f32, float32x4x2_t, float32x4x3_t, float32x4x4_t); + + tuple!(i64, int64x1x2_t, int64x1x3_t, int64x1x4_t); + tuple!(i64, int64x2x2_t, int64x2x3_t, int64x2x4_t); + tuple!(u64, uint64x1x2_t, uint64x1x3_t, uint64x1x4_t); + tuple!(u64, uint64x2x2_t, uint64x2x3_t, uint64x2x4_t); + tuple!(u64, poly64x1x2_t, poly64x1x3_t, poly64x1x4_t); + tuple!(u64, poly64x2x2_t, poly64x2x3_t, poly64x2x4_t); } #[cfg(any( @@ -71,4 +112,7 @@ mod aarch64 { from_transmute! { unsafe Simd => float64x1_t } from_transmute! { unsafe f64x2 => float64x2_t } + + tuple!(f64, float64x1x2_t, float64x1x3_t, float64x1x4_t); + tuple!(f64, float64x2x2_t, float64x2x3_t, float64x2x4_t); } From 61ae55e111690bc428e97a959adde0a3dcd48e6d Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Sat, 30 May 2026 14:30:22 -0400 Subject: [PATCH 06/31] Use `impl` restrictions over hand-sealed traits --- crates/core_simd/src/cast.rs | 55 +++++++--------------- crates/core_simd/src/lib.rs | 1 + crates/core_simd/src/masks.rs | 15 +++--- crates/core_simd/src/simd/num.rs | 4 -- crates/core_simd/src/simd/num/float.rs | 5 +- crates/core_simd/src/simd/num/int.rs | 5 +- crates/core_simd/src/simd/num/uint.rs | 5 +- crates/core_simd/src/simd/ptr.rs | 4 -- crates/core_simd/src/simd/ptr/const_ptr.rs | 5 +- crates/core_simd/src/simd/ptr/mut_ptr.rs | 5 +- crates/core_simd/src/to_bytes.rs | 11 +---- crates/core_simd/src/vector.rs | 37 +-------------- crates/std_float/src/lib.rs | 17 +------ 13 files changed, 38 insertions(+), 131 deletions(-) diff --git a/crates/core_simd/src/cast.rs b/crates/core_simd/src/cast.rs index 69dc7ba50d58d..81187ae68ad19 100644 --- a/crates/core_simd/src/cast.rs +++ b/crates/core_simd/src/cast.rs @@ -1,54 +1,35 @@ use crate::simd::SimdElement; -mod sealed { - /// Cast vector elements to other types. - /// - /// # Safety - /// Implementing this trait asserts that the type is a valid vector element for the `simd_cast` - /// or `simd_as` intrinsics. - pub unsafe trait Sealed {} -} -use sealed::Sealed; - /// Supporting trait for `Simd::cast`. Typically doesn't need to be used directly. -pub trait SimdCast: Sealed + SimdElement {} +/// +/// # Safety +/// Implementing this trait asserts that the type is a valid vector element for the `simd_cast` or +/// `simd_as` intrinsics. +pub impl(self) unsafe trait SimdCast: SimdElement {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for i8 {} -impl SimdCast for i8 {} +unsafe impl SimdCast for i8 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for i16 {} -impl SimdCast for i16 {} +unsafe impl SimdCast for i16 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for i32 {} -impl SimdCast for i32 {} +unsafe impl SimdCast for i32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for i64 {} -impl SimdCast for i64 {} +unsafe impl SimdCast for i64 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for isize {} -impl SimdCast for isize {} +unsafe impl SimdCast for isize {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for u8 {} -impl SimdCast for u8 {} +unsafe impl SimdCast for u8 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for u16 {} -impl SimdCast for u16 {} +unsafe impl SimdCast for u16 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for u32 {} -impl SimdCast for u32 {} +unsafe impl SimdCast for u32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for u64 {} -impl SimdCast for u64 {} +unsafe impl SimdCast for u64 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for usize {} -impl SimdCast for usize {} +unsafe impl SimdCast for usize {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for f16 {} -impl SimdCast for f16 {} +unsafe impl SimdCast for f16 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for f32 {} -impl SimdCast for f32 {} +unsafe impl SimdCast for f32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl Sealed for f64 {} -impl SimdCast for f64 {} +unsafe impl SimdCast for f64 {} diff --git a/crates/core_simd/src/lib.rs b/crates/core_simd/src/lib.rs index 413a886f6c5b8..e59b286f780da 100644 --- a/crates/core_simd/src/lib.rs +++ b/crates/core_simd/src/lib.rs @@ -4,6 +4,7 @@ f16, core_intrinsics, decl_macro, + impl_restriction, repr_simd, staged_api, prelude_import, diff --git a/crates/core_simd/src/masks.rs b/crates/core_simd/src/masks.rs index cb5d54020f7fc..90a00e016358e 100644 --- a/crates/core_simd/src/masks.rs +++ b/crates/core_simd/src/masks.rs @@ -29,7 +29,7 @@ macro_rules! impl_fix_endianness { impl_fix_endianness! { u8, u16, u32, u64 } -mod sealed { +mod private_methods { use super::*; /// Not only does this seal the `MaskElement` trait, but these functions prevent other traits @@ -38,7 +38,7 @@ mod sealed { /// For example, `eq` could be provided by requiring `MaskElement: PartialEq`, but that would /// prevent us from ever removing that bound, or from implementing `MaskElement` on /// non-`PartialEq` types in the future. - pub trait Sealed { + pub impl(super) trait PrivateMethods { fn valid(values: Simd) -> bool where Self: SimdElement; @@ -55,17 +55,20 @@ mod sealed { const FALSE: Self; } } -use sealed::Sealed; +use private_methods::PrivateMethods; /// Marker trait for types that may be used as SIMD mask elements. /// /// # Safety /// Type must be a signed integer. -pub unsafe trait MaskElement: SimdElement + SimdCast + Sealed {} +pub impl(self) unsafe trait MaskElement: + SimdElement + SimdCast + PrivateMethods +{ +} macro_rules! impl_element { { $ty:ty, $unsigned:ty } => { - impl Sealed for $ty { + impl PrivateMethods for $ty { #[inline] fn valid(value: Simd) -> bool { @@ -196,7 +199,7 @@ where pub unsafe fn from_simd_unchecked(value: Simd) -> Self { // Safety: the caller must confirm this invariant unsafe { - core::intrinsics::assume(::valid(value)); + core::intrinsics::assume(::valid(value)); } Self(value) } diff --git a/crates/core_simd/src/simd/num.rs b/crates/core_simd/src/simd/num.rs index 22a4802ec6cb5..d1186d3734b49 100644 --- a/crates/core_simd/src/simd/num.rs +++ b/crates/core_simd/src/simd/num.rs @@ -4,10 +4,6 @@ mod float; mod int; mod uint; -mod sealed { - pub trait Sealed {} -} - pub use float::*; pub use int::*; pub use uint::*; diff --git a/crates/core_simd/src/simd/num/float.rs b/crates/core_simd/src/simd/num/float.rs index 14a31b527872b..142740ad01560 100644 --- a/crates/core_simd/src/simd/num/float.rs +++ b/crates/core_simd/src/simd/num/float.rs @@ -1,11 +1,10 @@ -use super::sealed::Sealed; use crate::simd::{ Mask, Select, Simd, SimdCast, SimdElement, cmp::{SimdPartialEq, SimdPartialOrd}, }; /// Operations on SIMD vectors of floats. -pub trait SimdFloat: Copy + Sealed { +pub impl(self) trait SimdFloat: Copy { /// Mask type used for manipulating this SIMD vector type. type Mask; @@ -240,8 +239,6 @@ pub trait SimdFloat: Copy + Sealed { macro_rules! impl_trait { { $($ty:ty { bits: $bits_ty:ty, mask: $mask_ty:ty }),* } => { $( - impl Sealed for Simd<$ty, N> {} - impl SimdFloat for Simd<$ty, N> { type Mask = Mask<<$mask_ty as SimdElement>::Mask, N>; diff --git a/crates/core_simd/src/simd/num/int.rs b/crates/core_simd/src/simd/num/int.rs index eee54d3968808..ab556c14d6371 100644 --- a/crates/core_simd/src/simd/num/int.rs +++ b/crates/core_simd/src/simd/num/int.rs @@ -1,10 +1,9 @@ -use super::sealed::Sealed; use crate::simd::{ Mask, Select, Simd, SimdCast, SimdElement, cmp::SimdOrd, cmp::SimdPartialOrd, num::SimdUint, }; /// Operations on SIMD vectors of signed integers. -pub trait SimdInt: Copy + Sealed { +pub impl(self) trait SimdInt: Copy { /// Mask type used for manipulating this SIMD vector type. type Mask; @@ -241,8 +240,6 @@ pub trait SimdInt: Copy + Sealed { macro_rules! impl_trait { { $($ty:ident ($unsigned:ident)),* } => { $( - impl Sealed for Simd<$ty, N> {} - impl SimdInt for Simd<$ty, N> { type Mask = Mask<<$ty as SimdElement>::Mask, N>; type Scalar = $ty; diff --git a/crates/core_simd/src/simd/num/uint.rs b/crates/core_simd/src/simd/num/uint.rs index 606107a1f06f8..e4d383a0c21a5 100644 --- a/crates/core_simd/src/simd/num/uint.rs +++ b/crates/core_simd/src/simd/num/uint.rs @@ -1,8 +1,7 @@ -use super::sealed::Sealed; use crate::simd::{Simd, SimdCast, SimdElement, cmp::SimdOrd}; /// Operations on SIMD vectors of unsigned integers. -pub trait SimdUint: Copy + Sealed { +pub impl(self) trait SimdUint: Copy { /// Scalar type contained by this SIMD vector type. type Scalar; @@ -124,8 +123,6 @@ pub trait SimdUint: Copy + Sealed { macro_rules! impl_trait { { $($ty:ident ($signed:ident)),* } => { $( - impl Sealed for Simd<$ty, N> {} - impl SimdUint for Simd<$ty, N> { type Scalar = $ty; diff --git a/crates/core_simd/src/simd/ptr.rs b/crates/core_simd/src/simd/ptr.rs index a27367ceb791b..bb5d9c870f448 100644 --- a/crates/core_simd/src/simd/ptr.rs +++ b/crates/core_simd/src/simd/ptr.rs @@ -3,10 +3,6 @@ mod const_ptr; mod mut_ptr; -mod sealed { - pub trait Sealed {} -} - pub use const_ptr::*; pub use mut_ptr::*; diff --git a/crates/core_simd/src/simd/ptr/const_ptr.rs b/crates/core_simd/src/simd/ptr/const_ptr.rs index be2b9e6b4835f..4d21af5270b3e 100644 --- a/crates/core_simd/src/simd/ptr/const_ptr.rs +++ b/crates/core_simd/src/simd/ptr/const_ptr.rs @@ -1,8 +1,7 @@ -use super::sealed::Sealed; use crate::simd::{Mask, Simd, cmp::SimdPartialEq, num::SimdUint}; /// Operations on SIMD vectors of constant pointers. -pub trait SimdConstPtr: Copy + Sealed { +pub impl(self) trait SimdConstPtr: Copy { /// Vector of `usize` with the same number of elements. type Usize; @@ -65,8 +64,6 @@ pub trait SimdConstPtr: Copy + Sealed { fn wrapping_sub(self, count: Self::Usize) -> Self; } -impl Sealed for Simd<*const T, N> {} - impl SimdConstPtr for Simd<*const T, N> { type Usize = Simd; type Isize = Simd; diff --git a/crates/core_simd/src/simd/ptr/mut_ptr.rs b/crates/core_simd/src/simd/ptr/mut_ptr.rs index 78b5bef4d9740..b7b96d5e98722 100644 --- a/crates/core_simd/src/simd/ptr/mut_ptr.rs +++ b/crates/core_simd/src/simd/ptr/mut_ptr.rs @@ -1,8 +1,7 @@ -use super::sealed::Sealed; use crate::simd::{Mask, Simd, cmp::SimdPartialEq, num::SimdUint}; /// Operations on SIMD vectors of mutable pointers. -pub trait SimdMutPtr: Copy + Sealed { +pub impl(self) trait SimdMutPtr: Copy { /// Vector of `usize` with the same number of elements. type Usize; @@ -65,8 +64,6 @@ pub trait SimdMutPtr: Copy + Sealed { fn wrapping_sub(self, count: Self::Usize) -> Self; } -impl Sealed for Simd<*mut T, N> {} - impl SimdMutPtr for Simd<*mut T, N> { type Usize = Simd; type Isize = Simd; diff --git a/crates/core_simd/src/to_bytes.rs b/crates/core_simd/src/to_bytes.rs index 1fd285e457db8..becc4e5a93a3c 100644 --- a/crates/core_simd/src/to_bytes.rs +++ b/crates/core_simd/src/to_bytes.rs @@ -1,17 +1,10 @@ use crate::simd::{ - Simd, SimdElement, + Simd, num::{SimdFloat, SimdInt, SimdUint}, }; -mod sealed { - use super::*; - pub trait Sealed {} - impl Sealed for Simd {} -} -use sealed::Sealed; - /// Converts SIMD vectors to vectors of bytes -pub trait ToBytes: Sealed { +pub impl(self) trait ToBytes { /// This type, reinterpreted as bytes. type Bytes: Copy + Unpin diff --git a/crates/core_simd/src/vector.rs b/crates/core_simd/src/vector.rs index fbef69f267aa5..55de9dac4637f 100644 --- a/crates/core_simd/src/vector.rs +++ b/crates/core_simd/src/vector.rs @@ -1058,11 +1058,6 @@ where } } -mod sealed { - pub trait Sealed {} -} -use sealed::Sealed; - /// Marker trait for types that may be used as SIMD vector elements. /// /// # Safety @@ -1071,104 +1066,76 @@ use sealed::Sealed; /// Strictly, it is valid to impl if the vector will not be miscompiled. /// Practically, it is user-unfriendly to impl it if the vector won't compile, /// even when no soundness guarantees are broken by allowing the user to try. -pub unsafe trait SimdElement: Sealed + Copy { +pub impl(self) unsafe trait SimdElement: Copy { /// The mask element type corresponding to this element type. type Mask: MaskElement; } -impl Sealed for u8 {} - // Safety: u8 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for u8 { type Mask = i8; } -impl Sealed for u16 {} - // Safety: u16 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for u16 { type Mask = i16; } -impl Sealed for u32 {} - // Safety: u32 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for u32 { type Mask = i32; } -impl Sealed for u64 {} - // Safety: u64 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for u64 { type Mask = i64; } -impl Sealed for usize {} - // Safety: usize is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for usize { type Mask = isize; } -impl Sealed for i8 {} - // Safety: i8 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for i8 { type Mask = i8; } -impl Sealed for i16 {} - // Safety: i16 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for i16 { type Mask = i16; } -impl Sealed for i32 {} - // Safety: i32 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for i32 { type Mask = i32; } -impl Sealed for i64 {} - // Safety: i64 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for i64 { type Mask = i64; } -impl Sealed for isize {} - // Safety: isize is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for isize { type Mask = isize; } -impl Sealed for f16 {} - // Safety: f16 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for f16 { type Mask = i16; } -impl Sealed for f32 {} - // Safety: f32 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for f32 { type Mask = i32; } -impl Sealed for f64 {} - // Safety: f64 is a valid SIMD element type, and is supported by this API unsafe impl SimdElement for f64 { type Mask = i64; } -impl Sealed for *const T {} - // Safety: (thin) const pointers are valid SIMD element types, and are supported by this API // // Fat pointers may be supported in the future. @@ -1179,8 +1146,6 @@ where type Mask = isize; } -impl Sealed for *mut T {} - // Safety: (thin) mut pointers are valid SIMD element types, and are supported by this API // // Fat pointers may be supported in the future. diff --git a/crates/std_float/src/lib.rs b/crates/std_float/src/lib.rs index ff3525452231a..4714881bcdad3 100644 --- a/crates/std_float/src/lib.rs +++ b/crates/std_float/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(impl_restriction)] #![cfg_attr( feature = "as_crate", feature(core_intrinsics), @@ -14,16 +15,6 @@ use core::intrinsics::simd as intrinsics; use simd::Simd; -#[cfg(feature = "as_crate")] -mod experimental { - pub trait Sealed {} -} - -#[cfg(feature = "as_crate")] -use experimental as sealed; - -use crate::sealed::Sealed; - /// This trait provides a possibly-temporary implementation of float functions /// that may, in the absence of hardware support, canonicalize to calling an /// operating system's `math.h` dynamically-loaded library (also known as a @@ -43,7 +34,7 @@ use crate::sealed::Sealed; /// when either the compiler or its supporting runtime functions are improved. /// For now this trait is available to permit experimentation with SIMD float /// operations that may lack hardware support, such as `mul_add`. -pub trait StdFloat: Sealed + Sized { +pub impl(self) trait StdFloat: Sized { /// Elementwise fused multiply-add. Computes `(self * a) + b` with only one rounding error, /// yielding a more accurate result than an unfused multiply-add. /// @@ -170,10 +161,6 @@ pub trait StdFloat: Sealed + Sized { fn fract(self) -> Self; } -impl Sealed for Simd {} -impl Sealed for Simd {} -impl Sealed for Simd {} - impl StdFloat for Simd { #[inline] fn fract(self) -> Self { From ae33abea636f41988aca494ffa8f76688174e286 Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Sat, 20 Jun 2026 00:24:37 +0100 Subject: [PATCH 07/31] Add `Mask::last_set` method For symmetry with `Mask::first_set` --- crates/core_simd/src/masks.rs | 63 +++++++++++++++++++++++++++++++-- crates/core_simd/tests/masks.rs | 15 +++++--- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/crates/core_simd/src/masks.rs b/crates/core_simd/src/masks.rs index cb5d54020f7fc..847869c260a9b 100644 --- a/crates/core_simd/src/masks.rs +++ b/crates/core_simd/src/masks.rs @@ -361,8 +361,7 @@ where pub fn first_set(self) -> Option { // If bitmasks are efficient, using them is better if cfg!(target_feature = "sse") && N <= 64 { - let tz = self.to_bitmask().trailing_zeros(); - return if tz == 64 { None } else { Some(tz as usize) }; + return self.to_bitmask().lowest_one().map(|i| i as usize); } // To find the first set index: @@ -411,6 +410,66 @@ where Some(min_index) } } + + /// Finds the index of the last set element. + /// + /// ``` + /// # #![feature(portable_simd)] + /// # #[cfg(feature = "as_crate")] use core_simd::simd; + /// # #[cfg(not(feature = "as_crate"))] use core::simd; + /// # use simd::mask32x8; + /// assert_eq!(mask32x8::splat(false).last_set(), None); + /// assert_eq!(mask32x8::splat(true).last_set(), Some(7)); + /// + /// let mask = mask32x8::from_array([false, true, false, false, true, false, true, false]); + /// assert_eq!(mask.last_set(), Some(6)); + /// ``` + #[inline] + #[must_use = "method returns the index and does not mutate the original value"] + pub fn last_set(self) -> Option { + // If bitmasks are efficient, using them is better + if cfg!(target_feature = "sse") && N <= 64 { + return self.to_bitmask().highest_one().map(|i| i as usize); + } + + // To find the first set index: + // * create a vector 0..N + // * replace unset mask elements in that vector with -1 + // * perform _signed_ reduce-max + // * check if the result is -1 or an index + + let index: Simd = const { + let mut index = [0; N]; + let mut i = 0; + while i < N { + index[i] = i; + i += 1; + } + // Safety: the input and output are integer vectors + unsafe { core::intrinsics::simd::simd_cast(Simd::from_array(index)) } + }; + + // Safety: the input and output are integer vectors + let masked_index: Simd = + unsafe { core::intrinsics::simd::simd_or((!self).to_simd(), index) }; + + // Safety: the input is an integer vector + let max_index: T = unsafe { core::intrinsics::simd::simd_reduce_max(masked_index) }; + + if max_index.eq(T::TRUE) { + None + } else { + let max_index = max_index.to_usize(); + + // Allow eliminating bounds checks when using the index + // Safety: the index can't exceed the number of elements in the vector + unsafe { + core::hint::assert_unchecked(max_index < N); + } + + Some(max_index) + } + } } // vector/array conversion diff --git a/crates/core_simd/tests/masks.rs b/crates/core_simd/tests/masks.rs index 98a74be8e3955..6bcf02ee12075 100644 --- a/crates/core_simd/tests/masks.rs +++ b/crates/core_simd/tests/masks.rs @@ -138,14 +138,19 @@ macro_rules! test_mask_api { fn first_set() { for bitmask in 0..=u8::MAX { let mask = Mask::<$type, 8>::from_bitmask(bitmask as u64); - let expected = if bitmask == 0 { - None - } else { - Some(bitmask.trailing_zeros() as usize) - }; + let expected = bitmask.lowest_one().map(|i| i as usize); assert_eq!(mask.first_set(), expected); } } + + #[test] + fn last_set() { + for bitmask in 0..=u8::MAX { + let mask = Mask::<$type, 8>::from_bitmask(bitmask as u64); + let expected = bitmask.highest_one().map(|i| i as usize); + assert_eq!(mask.last_set(), expected); + } + } } } } From 30bdfe3a5f434b352e8860a722be64fe70d77658 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Fri, 19 Jun 2026 17:02:13 +0800 Subject: [PATCH 08/31] Optimize `swizzle_dyn` for LoongArch64 with N is 16 or 32 --- crates/core_simd/src/swizzle_dyn.rs | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/crates/core_simd/src/swizzle_dyn.rs b/crates/core_simd/src/swizzle_dyn.rs index cf37aff9a76d1..95fe086a4b4ab 100644 --- a/crates/core_simd/src/swizzle_dyn.rs +++ b/crates/core_simd/src/swizzle_dyn.rs @@ -48,6 +48,8 @@ impl Simd { target_endian = "little" ))] 16 => transize(armv7_neon_swizzle_u8x16, self, idxs), + #[cfg(all(target_arch = "loongarch64", target_feature = "lsx"))] + 16 => transize(loong64_lsx_swizzle, self, idxs), #[cfg(all(target_feature = "avx2", not(target_feature = "avx512vbmi")))] 32 => transize(avx2_pshufb, self, idxs), #[cfg(all(target_feature = "avx512vl", target_feature = "avx512vbmi"))] @@ -62,6 +64,8 @@ impl Simd { }; transize(swizzler, self, idxs) } + #[cfg(all(target_arch = "loongarch64", target_feature = "lasx"))] + 32 => transize(loong64_lasx_swizzle, self, idxs), // Notable absence: avx512bw pshufb shuffle #[cfg(all(target_feature = "avx512vl", target_feature = "avx512vbmi"))] 64 => { @@ -220,6 +224,38 @@ unsafe fn avx2_pshufb(bytes: Simd, idxs: Simd) -> Simd { } } +/// LoongArch64 LSX supports swizzling `u8x16` +/// +/// # Safety +/// This requires LoongArch LSX to work +#[cfg(all(target_arch = "loongarch64", target_feature = "lsx"))] +unsafe fn loong64_lsx_swizzle(bytes: Simd, idxs: Simd) -> Simd { + use core::arch::loongarch64::{lsx_vand_v, lsx_vshuf_b, lsx_vslei_bu}; + // SAFETY: Caller promised loongarch lsx support + unsafe { + let bytes = lsx_vshuf_b(bytes.into(), bytes.into(), idxs.into()); + let mask = lsx_vslei_bu::<15>(idxs.into()); + lsx_vand_v(bytes, mask).into() + } +} + +/// LoongArch64 LASX supports swizzling `u8x32` +/// +/// # Safety +/// This requires LoongArch LASX to work +#[cfg(all(target_arch = "loongarch64", target_feature = "lasx"))] +unsafe fn loong64_lasx_swizzle(bytes: Simd, idxs: Simd) -> Simd { + use core::arch::loongarch64::{lasx_xvand_v, lasx_xvpermi_q, lasx_xvshuf_b, lasx_xvslei_bu}; + // SAFETY: Caller promised loongarch lasx support + unsafe { + let lolo = lasx_xvpermi_q::<0x00>(bytes.into(), bytes.into()); + let hihi = lasx_xvpermi_q::<0x11>(bytes.into(), bytes.into()); + let bytes = lasx_xvshuf_b(hihi, lolo, idxs.into()); + let mask = lasx_xvslei_bu::<31>(idxs.into()); + lasx_xvand_v(bytes, mask).into() + } +} + /// This sets up a call to an architecture-specific function, and in doing so /// it persuades rustc that everything is the correct size. Which it is. /// This would not be needed if one could convince Rust that, by matching on N, From 8a07fcc3f6cb5652bdba7d8f786ecf7d370ae3d4 Mon Sep 17 00:00:00 2001 From: uselessgoddess Date: Sat, 18 Jul 2026 12:09:58 +0300 Subject: [PATCH 09/31] fix base_urls with periods --- src/librustdoc/passes/lint/bare_urls.rs | 8 ++++ tests/rustdoc-ui/lints/bare-urls.fixed | 10 ++++ tests/rustdoc-ui/lints/bare-urls.rs | 10 ++++ tests/rustdoc-ui/lints/bare-urls.stderr | 62 ++++++++++++++++++++++--- 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs index 44fff58eb2618..0928980e390a8 100644 --- a/src/librustdoc/passes/lint/bare_urls.rs +++ b/src/librustdoc/passes/lint/bare_urls.rs @@ -111,6 +111,14 @@ fn find_raw_urls( url_range.start -= 1; url_range.end += 1; without_brackets = Some(match_.as_str()); + } else { + // Periods are valid in URLs, but very uncommon as the last character of one, while + // being very common as sentence punctuation right after one. Leave any trailing + // period out of the link, so that `Visit https://example.com/docs.` is linkified as + // `Visit .`. + let trailing_periods = + match_.as_str().len() - match_.as_str().trim_end_matches('.').len(); + url_range.end -= trailing_periods; } f(cx, "this URL is not a hyperlink", url_range, without_brackets); } diff --git a/tests/rustdoc-ui/lints/bare-urls.fixed b/tests/rustdoc-ui/lints/bare-urls.fixed index ac63e291c5b04..996214b5ff14f 100644 --- a/tests/rustdoc-ui/lints/bare-urls.fixed +++ b/tests/rustdoc-ui/lints/bare-urls.fixed @@ -69,6 +69,16 @@ pub mod foo { pub fn bar() {} } +/// Visit . +//~^ ERROR this URL is not a hyperlink +/// Two sentences. . And more text. +//~^ ERROR this URL is not a hyperlink +/// Trailing ellipsis ... +//~^ ERROR this URL is not a hyperlink +/// A period in the middle of is part of the URL. +//~^ ERROR this URL is not a hyperlink +pub fn trailing_period() {} + /// //~^ ERROR this URL is not a hyperlink /// [ ] diff --git a/tests/rustdoc-ui/lints/bare-urls.rs b/tests/rustdoc-ui/lints/bare-urls.rs index a70a3ec822e4a..9b4fe68e00322 100644 --- a/tests/rustdoc-ui/lints/bare-urls.rs +++ b/tests/rustdoc-ui/lints/bare-urls.rs @@ -69,6 +69,16 @@ pub mod foo { pub fn bar() {} } +/// Visit https://example.com/docs. +//~^ ERROR this URL is not a hyperlink +/// Two sentences. https://example.com. And more text. +//~^ ERROR this URL is not a hyperlink +/// Trailing ellipsis https://example.com/docs... +//~^ ERROR this URL is not a hyperlink +/// A period in the middle of https://example.com/a.b is part of the URL. +//~^ ERROR this URL is not a hyperlink +pub fn trailing_period() {} + /// [https://bloob.blob] //~^ ERROR this URL is not a hyperlink /// [ https://bloob.blob ] diff --git a/tests/rustdoc-ui/lints/bare-urls.stderr b/tests/rustdoc-ui/lints/bare-urls.stderr index 16c2f33fdfb1a..05ddd2ed42ab1 100644 --- a/tests/rustdoc-ui/lints/bare-urls.stderr +++ b/tests/rustdoc-ui/lints/bare-urls.stderr @@ -244,7 +244,55 @@ LL | #[doc = ""] | + + error: this URL is not a hyperlink - --> $DIR/bare-urls.rs:72:5 + --> $DIR/bare-urls.rs:72:11 + | +LL | /// Visit https://example.com/docs. + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +help: use an automatic link instead + | +LL | /// Visit . + | + + + +error: this URL is not a hyperlink + --> $DIR/bare-urls.rs:74:20 + | +LL | /// Two sentences. https://example.com. And more text. + | ^^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +help: use an automatic link instead + | +LL | /// Two sentences. . And more text. + | + + + +error: this URL is not a hyperlink + --> $DIR/bare-urls.rs:76:23 + | +LL | /// Trailing ellipsis https://example.com/docs... + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +help: use an automatic link instead + | +LL | /// Trailing ellipsis ... + | + + + +error: this URL is not a hyperlink + --> $DIR/bare-urls.rs:78:31 + | +LL | /// A period in the middle of https://example.com/a.b is part of the URL. + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +help: use an automatic link instead + | +LL | /// A period in the middle of is part of the URL. + | + + + +error: this URL is not a hyperlink + --> $DIR/bare-urls.rs:82:5 | LL | /// [https://bloob.blob] | ^^^^^^^^^^^^^^^^^^^^ @@ -257,7 +305,7 @@ LL + /// | error: this URL is not a hyperlink - --> $DIR/bare-urls.rs:74:7 + --> $DIR/bare-urls.rs:84:7 | LL | /// [ https://bloob.blob ] | ^^^^^^^^^^^^^^^^^^ @@ -269,7 +317,7 @@ LL | /// [ ] | + + error: this URL is not a hyperlink - --> $DIR/bare-urls.rs:76:7 + --> $DIR/bare-urls.rs:86:7 | LL | /// [ https://bloob.blob] | ^^^^^^^^^^^^^^^^^^ @@ -281,7 +329,7 @@ LL | /// [ ] | + + error: this URL is not a hyperlink - --> $DIR/bare-urls.rs:78:6 + --> $DIR/bare-urls.rs:88:6 | LL | /// [https://bloob.blob ] | ^^^^^^^^^^^^^^^^^^ @@ -293,7 +341,7 @@ LL | /// [ ] | + + error: this URL is not a hyperlink - --> $DIR/bare-urls.rs:80:6 + --> $DIR/bare-urls.rs:90:6 | LL | /// [https://bloob.blob | ^^^^^^^^^^^^^^^^^^ @@ -305,7 +353,7 @@ LL | /// [ | + + error: this URL is not a hyperlink - --> $DIR/bare-urls.rs:82:5 + --> $DIR/bare-urls.rs:92:5 | LL | /// https://bloob.blob] | ^^^^^^^^^^^^^^^^^^ @@ -316,5 +364,5 @@ help: use an automatic link instead LL | /// ] | + + -error: aborting due to 26 previous errors +error: aborting due to 30 previous errors From e46cf87f6f993b4185859752dc8d85e30c03ba60 Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sun, 31 May 2026 16:25:07 +0000 Subject: [PATCH 10/31] Extract coroutine_closure_output_coroutine. --- .../src/traits/project.rs | 170 ++++++++---------- 1 file changed, 77 insertions(+), 93 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 7a681a0b64e9d..e0edc76682a3b 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -1651,49 +1651,15 @@ fn confirm_closure_candidate<'cx, 'tcx>( // I didn't see a good way to unify those. ty::CoroutineClosure(def_id, args) => { let args = args.as_coroutine_closure(); - let kind_ty = args.kind_ty(); Unnormalized::new_wip(args.coroutine_closure_sig().map_bound(|sig| { - // If we know the kind and upvars, use that directly. - // Otherwise, defer to `AsyncFnKindHelper::Upvars` to delay - // the projection, like the `AsyncFn*` traits do. - let output_ty = if let Some(_) = kind_ty.to_opt_closure_kind() - // Fall back to projection if upvars aren't constrained - && !args.tupled_upvars_ty().is_ty_var() - { - sig.to_coroutine_given_kind_and_upvars( - tcx, - args.parent_args(), - tcx.coroutine_for_closure(def_id), - ty::ClosureKind::FnOnce, - tcx.lifetimes.re_static, - args.tupled_upvars_ty(), - args.coroutine_captures_by_ref_ty(), - ) - } else { - let upvars_projection_def_id = - tcx.require_lang_item(LangItem::AsyncFnKindUpvars, obligation.cause.span); - let tupled_upvars_ty = Ty::new_projection( - tcx, - ty::IsRigid::No, - upvars_projection_def_id, - [ - ty::GenericArg::from(kind_ty), - Ty::from_closure_kind(tcx, ty::ClosureKind::FnOnce).into(), - tcx.lifetimes.re_static.into(), - sig.tupled_inputs_ty.into(), - args.tupled_upvars_ty().into(), - args.coroutine_captures_by_ref_ty().into(), - ], - ); - sig.to_coroutine( - tcx, - args.parent_args(), - Ty::from_closure_kind(tcx, ty::ClosureKind::FnOnce), - tcx.coroutine_for_closure(def_id), - tupled_upvars_ty, - ) - }; - + let output_ty = coroutine_closure_output_coroutine( + tcx, + obligation, + ty::ClosureKind::FnOnce, + tcx.lifetimes.re_static, + def_id, + args, + ); tcx.mk_fn_sig([sig.tupled_inputs_ty], output_ty, sig.fn_sig_kind) })) } @@ -1770,60 +1736,12 @@ fn confirm_async_closure_candidate<'cx, 'tcx>( let poly_cache_entry = match *self_ty.kind() { ty::CoroutineClosure(def_id, args) => { let args = args.as_coroutine_closure(); - let kind_ty = args.kind_ty(); let sig = args.coroutine_closure_sig().skip_binder(); let term = match item_name { - sym::CallOnceFuture | sym::CallRefFuture => { - if let Some(closure_kind) = kind_ty.to_opt_closure_kind() - // Fall back to projection if upvars aren't constrained - && !args.tupled_upvars_ty().is_ty_var() - { - if !closure_kind.extends(goal_kind) { - bug!("we should not be confirming if the closure kind is not met"); - } - sig.to_coroutine_given_kind_and_upvars( - tcx, - args.parent_args(), - tcx.coroutine_for_closure(def_id), - goal_kind, - env_region, - args.tupled_upvars_ty(), - args.coroutine_captures_by_ref_ty(), - ) - } else { - let upvars_projection_def_id = tcx - .require_lang_item(LangItem::AsyncFnKindUpvars, obligation.cause.span); - // When we don't know the closure kind (and therefore also the closure's upvars, - // which are computed at the same time), we must delay the computation of the - // generator's upvars. We do this using the `AsyncFnKindHelper`, which as a trait - // goal functions similarly to the old `ClosureKind` predicate, and ensures that - // the goal kind <= the closure kind. As a projection `AsyncFnKindHelper::Upvars` - // will project to the right upvars for the generator, appending the inputs and - // coroutine upvars respecting the closure kind. - // N.B. No need to register a `AsyncFnKindHelper` goal here, it's already in `nested`. - let tupled_upvars_ty = Ty::new_projection( - tcx, - ty::IsRigid::No, - upvars_projection_def_id, - [ - ty::GenericArg::from(kind_ty), - Ty::from_closure_kind(tcx, goal_kind).into(), - env_region.into(), - sig.tupled_inputs_ty.into(), - args.tupled_upvars_ty().into(), - args.coroutine_captures_by_ref_ty().into(), - ], - ); - sig.to_coroutine( - tcx, - args.parent_args(), - Ty::from_closure_kind(tcx, goal_kind), - tcx.coroutine_for_closure(def_id), - tupled_upvars_ty, - ) - } - } + sym::CallOnceFuture | sym::CallRefFuture => coroutine_closure_output_coroutine( + tcx, obligation, goal_kind, env_region, def_id, args, + ), sym::Output => sig.return_ty, name => bug!("no such associated type: {name}"), }; @@ -1912,6 +1830,72 @@ fn confirm_async_closure_candidate<'cx, 'tcx>( .with_addl_obligations(nested) } +/// Given a `CoroutineClosure(def_id, args)`, interpret it as a closure, +/// and return its output type for the given `goal_kind` and `env_region`. +fn coroutine_closure_output_coroutine<'tcx>( + tcx: TyCtxt<'tcx>, + obligation: &ProjectionTermObligation<'tcx>, + goal_kind: ty::ClosureKind, + env_region: ty::Region<'tcx>, + def_id: DefId, + args: ty::CoroutineClosureArgs>, +) -> Ty<'tcx> { + let kind_ty = args.kind_ty(); + let sig = args.coroutine_closure_sig().skip_binder(); + + // If we know the kind and upvars, use that directly. + // Otherwise, defer to `AsyncFnKindHelper::Upvars` to delay + // the projection, like the `AsyncFn*` traits do. + if let Some(closure_kind) = kind_ty.to_opt_closure_kind() + // Fall back to projection if upvars aren't constrained + && !args.tupled_upvars_ty().is_ty_var() + { + if !closure_kind.extends(goal_kind) { + bug!("we should not be confirming if the closure kind is not met"); + } + sig.to_coroutine_given_kind_and_upvars( + tcx, + args.parent_args(), + tcx.coroutine_for_closure(def_id), + goal_kind, + env_region, + args.tupled_upvars_ty(), + args.coroutine_captures_by_ref_ty(), + ) + } else { + let upvars_projection_def_id = + tcx.require_lang_item(LangItem::AsyncFnKindUpvars, obligation.cause.span); + // When we don't know the closure kind (and therefore also the closure's upvars, + // which are computed at the same time), we must delay the computation of the + // generator's upvars. We do this using the `AsyncFnKindHelper`, which as a trait + // goal functions similarly to the old `ClosureKind` predicate, and ensures that + // the goal kind <= the closure kind. As a projection `AsyncFnKindHelper::Upvars` + // will project to the right upvars for the generator, appending the inputs and + // coroutine upvars respecting the closure kind. + // N.B. No need to register a `AsyncFnKindHelper` goal here, it's already in `nested`. + let tupled_upvars_ty = Ty::new_projection( + tcx, + ty::IsRigid::No, + upvars_projection_def_id, + [ + ty::GenericArg::from(kind_ty), + Ty::from_closure_kind(tcx, goal_kind).into(), + env_region.into(), + sig.tupled_inputs_ty.into(), + args.tupled_upvars_ty().into(), + args.coroutine_captures_by_ref_ty().into(), + ], + ); + sig.to_coroutine( + tcx, + args.parent_args(), + Ty::from_closure_kind(tcx, goal_kind), + tcx.coroutine_for_closure(def_id), + tupled_upvars_ty, + ) + } +} + fn confirm_async_fn_kind_helper_candidate<'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, obligation: &ProjectionTermObligation<'tcx>, From 2a4115deca0eca1345048b475b3ea87b29de3dbe Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Mon, 1 Jun 2026 09:22:31 +0000 Subject: [PATCH 11/31] Extract pretty_print_closure_inner. Closures, coroutines, coroutine witnesses and coroutine closures use the same syntax. This used to be copy-paste but gradually departed. --- compiler/rustc_middle/src/ty/print/pretty.rs | 135 ++++++------------ ...block-control-flow-static-semantics.stderr | 4 +- .../async-closures/is-not-fn.current.stderr | 2 +- .../async-closures/is-not-fn.next.stderr | 2 +- ...er-ranked-auto-trait-16.assumptions.stderr | 4 +- ...ranked-auto-trait-16.no_assumptions.stderr | 4 +- ...outine-yielding-or-returning-itself.stderr | 4 +- .../ui/coroutine/resume-arg-late-bound.stderr | 2 +- .../type-mismatch-signature-deduction.stderr | 2 +- .../ui/impl-trait/issues/issue-78722-2.stderr | 2 +- tests/ui/impl-trait/issues/issue-78722.stderr | 2 +- .../ui/impl-trait/nested-return-type4.stderr | 2 +- .../generator_returned_from_fn.stderr | 4 +- ...t-suggest-boxing-async-closure-body.stderr | 4 +- tests/ui/suggestions/unnamable-types.stderr | 2 +- tests/ui/traits/next-solver/async.fail.stderr | 2 +- tests/ui/traits/next-solver/async.rs | 2 +- .../stalled-coroutine-obligations.stderr | 8 +- .../type-alias-impl-trait/issue-94429.stderr | 2 +- 19 files changed, 70 insertions(+), 119 deletions(-) diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 858a2f21c6b75..5c939cbe773cb 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -713,6 +713,40 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { }) } + fn pretty_print_closure_inner( + &mut self, + did: DefId, + args: ty::GenericArgsRef<'tcx>, + ) -> Result<(), PrintError> { + if self.should_truncate() { + write!(self, "@...") + } else if self.tcx().sess.opts.unstable_opts.span_free_formats { + write!(self, "@")?; + self.print_def_path(did, args) + } else if let Some(did) = did.as_local() { + let span = self.tcx().def_span(did); + let loc = if with_forced_trimmed_paths() { + self.tcx() + .sess + .source_map() + .span_to_short_string(span, RemapPathScopeComponents::DIAGNOSTICS) + } else { + self.tcx().sess.source_map().span_to_diagnostic_string(span) + }; + write!( + self, + "@{}", + // This may end up in stderr diagnostics but it may also be + // emitted into MIR. Hence we use the remapped path if + // available + loc + ) + } else { + write!(self, "@")?; + self.print_def_path(did, args) + } + } + fn pretty_print_type(&mut self, ty: Ty<'tcx>) -> Result<(), PrintError> { match *ty.kind() { ty::Bool => write!(self, "bool")?, @@ -904,22 +938,12 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { // // This will look like: // {async fn body of some_fn()} - let did_of_the_fn_item = self.tcx().parent(did); write!(self, " of ")?; + let did_of_the_fn_item = self.tcx().parent(did); self.print_def_path(did_of_the_fn_item, args)?; write!(self, "()")?; - } else if let Some(local_did) = did.as_local() { - let span = self.tcx().def_span(local_did); - write!( - self, - "@{}", - // This may end up in stderr diagnostics but it may also be emitted - // into MIR. Hence we use the remapped path if available - self.tcx().sess.source_map().span_to_diagnostic_string(span) - )?; } else { - write!(self, "@")?; - self.print_def_path(did, args)?; + self.pretty_print_closure_inner(did, args)?; } } else { self.print_def_path(did, args)?; @@ -937,63 +961,19 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { } ty::CoroutineWitness(did, args) => { write!(self, "{{")?; - if !self.tcx().sess.verbose_internals() { + if !self.should_print_verbose() { write!(self, "coroutine witness")?; - if let Some(did) = did.as_local() { - let span = self.tcx().def_span(did); - write!( - self, - "@{}", - // This may end up in stderr diagnostics but it may also be emitted - // into MIR. Hence we use the remapped path if available - self.tcx().sess.source_map().span_to_diagnostic_string(span) - )?; - } else { - write!(self, "@")?; - self.print_def_path(did, args)?; - } + self.pretty_print_closure_inner(did, args)?; } else { self.print_def_path(did, args)?; } - write!(self, "}}")? } ty::Closure(did, args) => { write!(self, "{{")?; if !self.should_print_verbose() { write!(self, "closure")?; - if self.should_truncate() { - write!(self, "@...}}")?; - return Ok(()); - } else { - if let Some(did) = did.as_local() { - if self.tcx().sess.opts.unstable_opts.span_free_formats { - write!(self, "@")?; - self.print_def_path(did.to_def_id(), args)?; - } else { - let span = self.tcx().def_span(did); - let loc = if with_forced_trimmed_paths() { - self.tcx().sess.source_map().span_to_short_string( - span, - RemapPathScopeComponents::DIAGNOSTICS, - ) - } else { - self.tcx().sess.source_map().span_to_diagnostic_string(span) - }; - write!( - self, - "@{}", - // This may end up in stderr diagnostics but it may also be - // emitted into MIR. Hence we use the remapped path if - // available - loc - )?; - } - } else { - write!(self, "@")?; - self.print_def_path(did, args)?; - } - } + self.pretty_print_closure_inner(did, args)?; } else { self.print_def_path(did, args)?; write!(self, " closure_kind_ty=")?; @@ -1011,43 +991,14 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { match self.tcx().coroutine_kind(self.tcx().coroutine_for_closure(did)).unwrap() { hir::CoroutineKind::Desugared( - hir::CoroutineDesugaring::Async, - hir::CoroutineSource::Closure, - ) => write!(self, "async closure")?, - hir::CoroutineKind::Desugared( - hir::CoroutineDesugaring::AsyncGen, + desugaring, hir::CoroutineSource::Closure, - ) => write!(self, "async gen closure")?, - hir::CoroutineKind::Desugared( - hir::CoroutineDesugaring::Gen, - hir::CoroutineSource::Closure, - ) => write!(self, "gen closure")?, + ) => write!(self, "{desugaring}closure")?, _ => unreachable!( "coroutine from coroutine-closure should have CoroutineSource::Closure" ), - } - if let Some(did) = did.as_local() { - if self.tcx().sess.opts.unstable_opts.span_free_formats { - write!(self, "@")?; - self.print_def_path(did.to_def_id(), args)?; - } else { - let span = self.tcx().def_span(did); - // This may end up in stderr diagnostics but it may also be emitted - // into MIR. Hence we use the remapped path if available - let loc = if with_forced_trimmed_paths() { - self.tcx().sess.source_map().span_to_short_string( - span, - RemapPathScopeComponents::DIAGNOSTICS, - ) - } else { - self.tcx().sess.source_map().span_to_diagnostic_string(span) - }; - write!(self, "@{loc}")?; - } - } else { - write!(self, "@")?; - self.print_def_path(did, args)?; - } + }; + self.pretty_print_closure_inner(did, args)?; } else { self.print_def_path(did, args)?; write!(self, " closure_kind_ty=")?; diff --git a/tests/ui/async-await/async-block-control-flow-static-semantics.stderr b/tests/ui/async-await/async-block-control-flow-static-semantics.stderr index b64690bae6cde..5a379cc83447c 100644 --- a/tests/ui/async-await/async-block-control-flow-static-semantics.stderr +++ b/tests/ui/async-await/async-block-control-flow-static-semantics.stderr @@ -10,7 +10,7 @@ LL | | return 0u8; LL | | } | |_^ expected `u8`, found `()` -error[E0271]: expected `{async block@$DIR/async-block-control-flow-static-semantics.rs:23:17: 23:22}` to be a future that resolves to `()`, but it resolves to `u8` +error[E0271]: expected `{async block@async-block-control-flow-static-semantics.rs:23:17}` to be a future that resolves to `()`, but it resolves to `u8` --> $DIR/async-block-control-flow-static-semantics.rs:26:39 | LL | let _: &dyn Future = █ @@ -26,7 +26,7 @@ LL | fn return_targets_async_block_not_fn() -> u8 { | | | implicitly returns `()` as its body has no tail or `return` expression -error[E0271]: expected `{async block@$DIR/async-block-control-flow-static-semantics.rs:14:17: 14:22}` to be a future that resolves to `()`, but it resolves to `u8` +error[E0271]: expected `{async block@async-block-control-flow-static-semantics.rs:14:17}` to be a future that resolves to `()`, but it resolves to `u8` --> $DIR/async-block-control-flow-static-semantics.rs:17:39 | LL | let _: &dyn Future = █ diff --git a/tests/ui/async-await/async-closures/is-not-fn.current.stderr b/tests/ui/async-await/async-closures/is-not-fn.current.stderr index 0fab1c15f27df..19d5f9966ffdc 100644 --- a/tests/ui/async-await/async-closures/is-not-fn.current.stderr +++ b/tests/ui/async-await/async-closures/is-not-fn.current.stderr @@ -1,4 +1,4 @@ -error[E0271]: expected `{async closure@is-not-fn.rs:8:14}` to return `()`, but it returns `{async closure body@$DIR/is-not-fn.rs:8:23: 8:25}` +error[E0271]: expected `{async closure@is-not-fn.rs:8:14}` to return `()`, but it returns `{async closure body@is-not-fn.rs:8:23}` --> $DIR/is-not-fn.rs:8:14 | LL | needs_fn(async || {}); diff --git a/tests/ui/async-await/async-closures/is-not-fn.next.stderr b/tests/ui/async-await/async-closures/is-not-fn.next.stderr index 0fab1c15f27df..19d5f9966ffdc 100644 --- a/tests/ui/async-await/async-closures/is-not-fn.next.stderr +++ b/tests/ui/async-await/async-closures/is-not-fn.next.stderr @@ -1,4 +1,4 @@ -error[E0271]: expected `{async closure@is-not-fn.rs:8:14}` to return `()`, but it returns `{async closure body@$DIR/is-not-fn.rs:8:23: 8:25}` +error[E0271]: expected `{async closure@is-not-fn.rs:8:14}` to return `()`, but it returns `{async closure body@is-not-fn.rs:8:23}` --> $DIR/is-not-fn.rs:8:14 | LL | needs_fn(async || {}); diff --git a/tests/ui/async-await/higher-ranked-auto-trait-16.assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-16.assumptions.stderr index 412c31b1bd843..dc6183a45cef5 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-16.assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-16.assumptions.stderr @@ -6,7 +6,7 @@ LL | | commit_if_ok(&mut ctxt, async |_| todo!()).await; LL | | }); | |______^ implementation of `AsyncFnOnce` is not general enough | - = note: `{async closure@$DIR/higher-ranked-auto-trait-16.rs:19:33: 19:42}` must implement `AsyncFnOnce<(&mut Ctxt<'1>,)>`, for any two lifetimes `'0` and `'1`... + = note: `{async closure@...}` must implement `AsyncFnOnce<(&mut Ctxt<'1>,)>`, for any two lifetimes `'0` and `'1`... = note: ...but it actually implements `AsyncFnOnce<(&mut Ctxt<'_>,)>` error: implementation of `AsyncFnOnce` is not general enough @@ -17,7 +17,7 @@ LL | | commit_if_ok(&mut ctxt, async |_| todo!()).await; LL | | }); | |______^ implementation of `AsyncFnOnce` is not general enough | - = note: `{async closure@$DIR/higher-ranked-auto-trait-16.rs:19:33: 19:42}` must implement `AsyncFnOnce<(&mut Ctxt<'1>,)>`, for any two lifetimes `'0` and `'1`... + = note: `{async closure@...}` must implement `AsyncFnOnce<(&mut Ctxt<'1>,)>`, for any two lifetimes `'0` and `'1`... = note: ...but it actually implements `AsyncFnOnce<(&mut Ctxt<'_>,)>` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` diff --git a/tests/ui/async-await/higher-ranked-auto-trait-16.no_assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-16.no_assumptions.stderr index 412c31b1bd843..dc6183a45cef5 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-16.no_assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-16.no_assumptions.stderr @@ -6,7 +6,7 @@ LL | | commit_if_ok(&mut ctxt, async |_| todo!()).await; LL | | }); | |______^ implementation of `AsyncFnOnce` is not general enough | - = note: `{async closure@$DIR/higher-ranked-auto-trait-16.rs:19:33: 19:42}` must implement `AsyncFnOnce<(&mut Ctxt<'1>,)>`, for any two lifetimes `'0` and `'1`... + = note: `{async closure@...}` must implement `AsyncFnOnce<(&mut Ctxt<'1>,)>`, for any two lifetimes `'0` and `'1`... = note: ...but it actually implements `AsyncFnOnce<(&mut Ctxt<'_>,)>` error: implementation of `AsyncFnOnce` is not general enough @@ -17,7 +17,7 @@ LL | | commit_if_ok(&mut ctxt, async |_| todo!()).await; LL | | }); | |______^ implementation of `AsyncFnOnce` is not general enough | - = note: `{async closure@$DIR/higher-ranked-auto-trait-16.rs:19:33: 19:42}` must implement `AsyncFnOnce<(&mut Ctxt<'1>,)>`, for any two lifetimes `'0` and `'1`... + = note: `{async closure@...}` must implement `AsyncFnOnce<(&mut Ctxt<'1>,)>`, for any two lifetimes `'0` and `'1`... = note: ...but it actually implements `AsyncFnOnce<(&mut Ctxt<'_>,)>` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` diff --git a/tests/ui/coroutine/coroutine-yielding-or-returning-itself.stderr b/tests/ui/coroutine/coroutine-yielding-or-returning-itself.stderr index 0c153e2d0af36..1b640ebe2b894 100644 --- a/tests/ui/coroutine/coroutine-yielding-or-returning-itself.stderr +++ b/tests/ui/coroutine/coroutine-yielding-or-returning-itself.stderr @@ -1,4 +1,4 @@ -error[E0271]: type mismatch resolving `<{coroutine@$DIR/coroutine-yielding-or-returning-itself.rs:15:47: 15:49} as Coroutine>::Return == {coroutine@$DIR/coroutine-yielding-or-returning-itself.rs:15:47: 15:49}` +error[E0271]: type mismatch resolving `<{coroutine@coroutine-yielding-or-returning-itself.rs:15:47} as Coroutine>::Return == {coroutine@coroutine-yielding-or-returning-itself.rs:15:47}` --> $DIR/coroutine-yielding-or-returning-itself.rs:15:47 | LL | want_cyclic_coroutine_return(#[coroutine] || { @@ -23,7 +23,7 @@ LL | pub fn want_cyclic_coroutine_return(_: T) LL | where T: Coroutine | ^^^^^^^^^^ required by this bound in `want_cyclic_coroutine_return` -error[E0271]: type mismatch resolving `<{coroutine@$DIR/coroutine-yielding-or-returning-itself.rs:28:46: 28:48} as Coroutine>::Yield == {coroutine@$DIR/coroutine-yielding-or-returning-itself.rs:28:46: 28:48}` +error[E0271]: type mismatch resolving `<{coroutine@coroutine-yielding-or-returning-itself.rs:28:46} as Coroutine>::Yield == {coroutine@coroutine-yielding-or-returning-itself.rs:28:46}` --> $DIR/coroutine-yielding-or-returning-itself.rs:28:46 | LL | want_cyclic_coroutine_yield(#[coroutine] || { diff --git a/tests/ui/coroutine/resume-arg-late-bound.stderr b/tests/ui/coroutine/resume-arg-late-bound.stderr index 646abaf4f7bde..0a6fb8dfcbb6c 100644 --- a/tests/ui/coroutine/resume-arg-late-bound.stderr +++ b/tests/ui/coroutine/resume-arg-late-bound.stderr @@ -4,7 +4,7 @@ error: implementation of `Coroutine` is not general enough LL | test(gen); | ^^^^^^^^^ implementation of `Coroutine` is not general enough | - = note: `{coroutine@$DIR/resume-arg-late-bound.rs:11:28: 11:44}` must implement `Coroutine<&'1 mut bool>`, for any lifetime `'1`... + = note: `{coroutine@...}` must implement `Coroutine<&'1 mut bool>`, for any lifetime `'1`... = note: ...but it actually implements `Coroutine<&'2 mut bool>`, for some specific lifetime `'2` error: aborting due to 1 previous error diff --git a/tests/ui/coroutine/type-mismatch-signature-deduction.stderr b/tests/ui/coroutine/type-mismatch-signature-deduction.stderr index 64dc3a8b1f80c..9391325901ec6 100644 --- a/tests/ui/coroutine/type-mismatch-signature-deduction.stderr +++ b/tests/ui/coroutine/type-mismatch-signature-deduction.stderr @@ -18,7 +18,7 @@ LL | Ok(5) LL | Err(5) | ++++ + -error[E0271]: type mismatch resolving `<{coroutine@$DIR/type-mismatch-signature-deduction.rs:8:5: 8:7} as Coroutine>::Return == i32` +error[E0271]: type mismatch resolving `<{coroutine@type-mismatch-signature-deduction.rs:8:5} as Coroutine>::Return == i32` --> $DIR/type-mismatch-signature-deduction.rs:5:13 | LL | fn foo() -> impl Coroutine { diff --git a/tests/ui/impl-trait/issues/issue-78722-2.stderr b/tests/ui/impl-trait/issues/issue-78722-2.stderr index ede830bf72c8b..c2aeb0ab13664 100644 --- a/tests/ui/impl-trait/issues/issue-78722-2.stderr +++ b/tests/ui/impl-trait/issues/issue-78722-2.stderr @@ -12,7 +12,7 @@ LL | let f: F = async { 1 }; = note: expected opaque type `F` found `async` block `{async block@$DIR/issue-78722-2.rs:17:20: 17:25}` -error[E0271]: expected `{async block@$DIR/issue-78722-2.rs:14:13: 14:18}` to be a future that resolves to `u8`, but it resolves to `()` +error[E0271]: expected `{async block@issue-78722-2.rs:14:13}` to be a future that resolves to `u8`, but it resolves to `()` --> $DIR/issue-78722-2.rs:12:30 | LL | fn concrete_use() -> F { diff --git a/tests/ui/impl-trait/issues/issue-78722.stderr b/tests/ui/impl-trait/issues/issue-78722.stderr index 84c7dfb033849..9244d18c36b49 100644 --- a/tests/ui/impl-trait/issues/issue-78722.stderr +++ b/tests/ui/impl-trait/issues/issue-78722.stderr @@ -8,7 +8,7 @@ LL | let f: F = async { 1 }; = help: add `#![feature(const_async_blocks)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0271]: expected `{async block@$DIR/issue-78722.rs:11:13: 11:18}` to be a future that resolves to `u8`, but it resolves to `()` +error[E0271]: expected `{async block@issue-78722.rs:11:13}` to be a future that resolves to `u8`, but it resolves to `()` --> $DIR/issue-78722.rs:9:30 | LL | fn concrete_use() -> F { diff --git a/tests/ui/impl-trait/nested-return-type4.stderr b/tests/ui/impl-trait/nested-return-type4.stderr index 407800eff1894..63f3bfcdda950 100644 --- a/tests/ui/impl-trait/nested-return-type4.stderr +++ b/tests/ui/impl-trait/nested-return-type4.stderr @@ -4,7 +4,7 @@ error[E0700]: hidden type for `impl Future` captures lifeti LL | fn test<'s: 's>(s: &'s str) -> impl std::future::Future { | -- --------------------------------------------- opaque type defined here | | - | hidden type `{async block@$DIR/nested-return-type4.rs:4:5: 4:15}` captures the lifetime `'s` as defined here + | hidden type `{async block@...}` captures the lifetime `'s` as defined here LL | async move { let _s = s; } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | diff --git a/tests/ui/iterators/generator_returned_from_fn.stderr b/tests/ui/iterators/generator_returned_from_fn.stderr index 8bec119ddbc01..f4620b958f0c6 100644 --- a/tests/ui/iterators/generator_returned_from_fn.stderr +++ b/tests/ui/iterators/generator_returned_from_fn.stderr @@ -28,7 +28,7 @@ error[E0700]: hidden type for `impl FnOnce() -> impl Iterator` captu LL | fn capture_move_once(a: &u32) -> impl FnOnce() -> impl Iterator { | ---- ------------------------------------------ opaque type defined here | | - | hidden type `{gen closure@$DIR/generator_returned_from_fn.rs:43:13: 43:20}` captures the anonymous lifetime defined here + | hidden type `{gen closure@...}` captures the anonymous lifetime defined here LL | iter! { move || { | _____________^ LL | | @@ -49,7 +49,7 @@ error[E0700]: hidden type for `impl Iterator` captures lifetime that LL | fn capture_move_once(a: &u32) -> impl FnOnce() -> impl Iterator { | ---- ------------------------- opaque type defined here | | - | hidden type `{gen closure body@$DIR/generator_returned_from_fn.rs:43:21: 50:6}` captures the anonymous lifetime defined here + | hidden type `{gen closure body@...}` captures the anonymous lifetime defined here LL | iter! { move || { | _____________^ LL | | diff --git a/tests/ui/suggestions/dont-suggest-boxing-async-closure-body.stderr b/tests/ui/suggestions/dont-suggest-boxing-async-closure-body.stderr index abf8e2d824b5d..c74c9b7b5cf3a 100644 --- a/tests/ui/suggestions/dont-suggest-boxing-async-closure-body.stderr +++ b/tests/ui/suggestions/dont-suggest-boxing-async-closure-body.stderr @@ -1,4 +1,4 @@ -error[E0271]: expected `{async closure@dont-suggest-boxing-async-closure-body.rs:9:9}` to return `Box<_>`, but it returns `{async closure body@$DIR/dont-suggest-boxing-async-closure-body.rs:9:23: 9:25}` +error[E0271]: expected `{async closure@dont-suggest-boxing-async-closure-body.rs:9:9}` to return `Box<_>`, but it returns `{async closure body@dont-suggest-boxing-async-closure-body.rs:9:23}` --> $DIR/dont-suggest-boxing-async-closure-body.rs:9:9 | LL | foo(async move || {}); @@ -18,7 +18,7 @@ error[E0308]: mismatched types --> $DIR/dont-suggest-boxing-async-closure-body.rs:11:9 | LL | bar(async move || {}); - | --- ^^^^^^^^^^^^^^^^ expected `Box _>`, found `{async closure@dont-suggest-boxing-async-closure-body.rs:11:9}` + | --- ^^^^^^^^^^^^^^^^ expected `Box _>`, found `{async closure@...}` | | | arguments to this function are incorrect | diff --git a/tests/ui/suggestions/unnamable-types.stderr b/tests/ui/suggestions/unnamable-types.stderr index bcd1a905194ef..e8f12e7476119 100644 --- a/tests/ui/suggestions/unnamable-types.stderr +++ b/tests/ui/suggestions/unnamable-types.stderr @@ -58,7 +58,7 @@ error: missing type for `const` item LL | const G = #[coroutine] || -> i32 { yield 0; return 1; }; | ^ | -note: however, the inferred type `{coroutine@$DIR/unnamable-types.rs:37:24: 37:33}` cannot be named +note: however, the inferred type `{coroutine@unnamable-types.rs:37:24}` cannot be named --> $DIR/unnamable-types.rs:37:24 | LL | const G = #[coroutine] || -> i32 { yield 0; return 1; }; diff --git a/tests/ui/traits/next-solver/async.fail.stderr b/tests/ui/traits/next-solver/async.fail.stderr index bc89842d16a14..328f1bc7d4384 100644 --- a/tests/ui/traits/next-solver/async.fail.stderr +++ b/tests/ui/traits/next-solver/async.fail.stderr @@ -1,4 +1,4 @@ -error[E0271]: expected `{async block@$DIR/async.rs:12:17: 12:22}` to be a future that resolves to `i32`, but it resolves to `()` +error[E0271]: expected `{async block@async.rs:12:17}` to be a future that resolves to `i32`, but it resolves to `()` --> $DIR/async.rs:12:17 | LL | needs_async(async {}); diff --git a/tests/ui/traits/next-solver/async.rs b/tests/ui/traits/next-solver/async.rs index fded774354759..cc6a99baf8d8d 100644 --- a/tests/ui/traits/next-solver/async.rs +++ b/tests/ui/traits/next-solver/async.rs @@ -10,7 +10,7 @@ fn needs_async(_: impl Future) {} #[cfg(fail)] fn main() { needs_async(async {}); - //[fail]~^ ERROR expected `{async block@$DIR/async.rs:12:17: 12:22}` to be a future that resolves to `i32`, but it resolves to `()` + //[fail]~^ ERROR expected `{async block@async.rs:12:17}` to be a future that resolves to `i32`, but it resolves to `()` } #[cfg(pass)] diff --git a/tests/ui/traits/next-solver/stalled-coroutine-obligations.stderr b/tests/ui/traits/next-solver/stalled-coroutine-obligations.stderr index 6fc64fe4768a5..8da83ef139709 100644 --- a/tests/ui/traits/next-solver/stalled-coroutine-obligations.stderr +++ b/tests/ui/traits/next-solver/stalled-coroutine-obligations.stderr @@ -1,22 +1,22 @@ -error[E0271]: type mismatch resolving `T == {async block@$DIR/stalled-coroutine-obligations.rs:18:18: 18:23}` +error[E0271]: type mismatch resolving `T == {async block@stalled-coroutine-obligations.rs:18:18}` --> $DIR/stalled-coroutine-obligations.rs:18:14 | LL | let foo: T = async {}; | ^ types differ -error[E0271]: type mismatch resolving `U == {async block@$DIR/stalled-coroutine-obligations.rs:22:18: 22:23}` +error[E0271]: type mismatch resolving `U == {async block@stalled-coroutine-obligations.rs:22:18}` --> $DIR/stalled-coroutine-obligations.rs:22:14 | LL | let bar: U = async {}; | ^ types differ -error[E0271]: type mismatch resolving `T == {async block@$DIR/stalled-coroutine-obligations.rs:33:18: 33:23}` +error[E0271]: type mismatch resolving `T == {async block@stalled-coroutine-obligations.rs:33:18}` --> $DIR/stalled-coroutine-obligations.rs:33:14 | LL | let foo: T = async { a }; | ^ types differ -error[E0271]: type mismatch resolving `impl Future + Send == {async block@$DIR/stalled-coroutine-obligations.rs:41:9: 41:19}` +error[E0271]: type mismatch resolving `impl Future + Send == {async block@stalled-coroutine-obligations.rs:41:9}` --> $DIR/stalled-coroutine-obligations.rs:39:43 | LL | fn stalled_send(&self, b: *mut ()) -> impl Future + Send { diff --git a/tests/ui/type-alias-impl-trait/issue-94429.stderr b/tests/ui/type-alias-impl-trait/issue-94429.stderr index f41b781f963d2..3f85d23144a4f 100644 --- a/tests/ui/type-alias-impl-trait/issue-94429.stderr +++ b/tests/ui/type-alias-impl-trait/issue-94429.stderr @@ -1,4 +1,4 @@ -error[E0271]: type mismatch resolving `<{coroutine@$DIR/issue-94429.rs:18:9: 18:16} as Coroutine>::Yield == ()` +error[E0271]: type mismatch resolving `<{coroutine@issue-94429.rs:18:9} as Coroutine>::Yield == ()` --> $DIR/issue-94429.rs:15:26 | LL | fn run(&mut self) -> Self::Coro { From f6d8df82f2ab506601705849332b122fd6e29126 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 12 May 2026 14:10:14 +1000 Subject: [PATCH 12/31] Move `std::io::buffered` to `alloc::io` --- .../src/io/buffered/bufreader.rs | 75 ++++++- .../src/io/buffered/bufreader/buffer.rs | 13 +- .../src/io/buffered/bufwriter.rs | 33 ++- .../src/io/buffered/linewriter.rs | 2 + .../src/io/buffered/linewritershim.rs | 4 +- library/alloc/src/io/buffered/mod.rs | 189 ++++++++++++++++++ library/alloc/src/io/mod.rs | 2 + library/std/src/io/buffered/mod.rs | 185 ----------------- library/std/src/io/mod.rs | 15 +- .../mut-borrow-needed-by-trait.stderr | 4 +- .../ui/suggestions/suggest-change-mut.stderr | 2 +- 11 files changed, 306 insertions(+), 218 deletions(-) rename library/{std => alloc}/src/io/buffered/bufreader.rs (87%) rename library/{std => alloc}/src/io/buffered/bufreader/buffer.rs (93%) rename library/{std => alloc}/src/io/buffered/bufwriter.rs (95%) rename library/{std => alloc}/src/io/buffered/linewriter.rs (98%) rename library/{std => alloc}/src/io/buffered/linewritershim.rs (99%) create mode 100644 library/alloc/src/io/buffered/mod.rs diff --git a/library/std/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs similarity index 87% rename from library/std/src/io/buffered/bufreader.rs rename to library/alloc/src/io/buffered/bufreader.rs index 8d122c8e76e5f..9a912be8e18f0 100644 --- a/library/std/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -1,12 +1,14 @@ mod buffer; -use buffer::Buffer; +pub(super) use buffer::Buffer; use crate::fmt; use crate::io::{ self, BorrowedCursor, BufRead, DEFAULT_BUF_SIZE, IoSliceMut, Read, Seek, SeekFrom, SizeHint, SpecReadByte, uninlined_slow_read_byte, }; +use crate::string::String; +use crate::vec::Vec; /// The `BufReader` struct adds buffering to any reader. /// @@ -27,8 +29,9 @@ use crate::io::{ /// unwrapping the `BufReader` with [`BufReader::into_inner`] can also cause /// data loss. /// -/// [`TcpStream::read`]: crate::net::TcpStream::read -/// [`TcpStream`]: crate::net::TcpStream +// FIXME(#74481): Hard-links required to link from `alloc` to `std` +/// [`TcpStream::read`]: ../../std/net/struct.TcpStream.html#method.read +/// [`TcpStream`]: ../../std/net/struct.TcpStream.html /// /// # Examples /// @@ -70,16 +73,21 @@ impl BufReader { /// Ok(()) /// } /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn new(inner: R) -> BufReader { BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) } - pub(crate) fn try_new_buffer() -> io::Result { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn try_new_buffer() -> io::Result { Buffer::try_with_capacity(DEFAULT_BUF_SIZE) } - pub(crate) fn with_buffer(inner: R, buf: Buffer) -> Self { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn with_buffer(inner: R, buf: Buffer) -> Self { Self { inner, buf } } @@ -99,6 +107,7 @@ impl BufReader { /// Ok(()) /// } /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(capacity: usize, inner: R) -> BufReader { BufReader { inner, buf: Buffer::with_capacity(capacity) } @@ -280,14 +289,17 @@ impl BufReader { /// Invalidates all data in the internal buffer. #[inline] - pub(in crate::io) fn discard_buffer(&mut self) { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn discard_buffer(&mut self) { self.buf.discard_buffer() } } // This is only used by a test which asserts that the initialization-tracking is correct. -#[cfg(test)] impl BufReader { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] #[allow(missing_docs)] pub fn initialized(&self) -> bool { self.buf.initialized() @@ -413,7 +425,28 @@ impl Read for BufReader { fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { let inner_buf = self.buffer(); buf.try_reserve(inner_buf.len())?; - buf.extend_from_slice(inner_buf); + + cfg_select! { + no_global_oom_handling => { + // SAFETY: + // * inner_buf and buf are non-overlapping + // * buf[..len] is already initialized + // * buf[len..len + count] is initialized by copy_nonoverlapping + // * len + count is within the capacity of buf based on the reservation completed above + unsafe { + let count = inner_buf.len(); + let len = buf.len(); + let src = inner_buf.as_ptr(); + let dst = buf.as_mut_ptr().add(len); + core::ptr::copy_nonoverlapping(src, dst, count); + buf.set_len(len + count); + } + } + _ => { + buf.extend_from_slice(inner_buf); + } + } + let nread = inner_buf.len(); self.discard_buffer(); Ok(nread + self.inner.read_to_end(buf)?) @@ -445,7 +478,31 @@ impl Read for BufReader { let mut bytes = Vec::new(); self.read_to_end(&mut bytes)?; let string = crate::str::from_utf8(&bytes).map_err(|_| io::Error::INVALID_UTF8)?; - *buf += string; + + cfg_select! { + no_global_oom_handling => { + // SAFETY: + // * string and buf are non-overlapping + // * buf[..len] is already initialized + // * buf[len..len + count] is initialized by copy_nonoverlapping + // * len + count is within the capacity of buf based on the reservation completed above + // * buf is appended with valid UTF-8 data and is initially valid UTF-8, therefore + // it is valid UTF-8 at all times. + unsafe { + let buf = buf.as_mut_vec(); + let count = string.len(); + let len = buf.len(); + let src = string.as_ptr(); + let dst = buf.as_mut_ptr().add(len); + core::ptr::copy_nonoverlapping(src, dst, count); + buf.set_len(len + count); + } + } + _ => { + *buf += string; + } + } + Ok(string.len()) } } diff --git a/library/std/src/io/buffered/bufreader/buffer.rs b/library/alloc/src/io/buffered/bufreader/buffer.rs similarity index 93% rename from library/std/src/io/buffered/bufreader/buffer.rs rename to library/alloc/src/io/buffered/bufreader/buffer.rs index f2efcc5ab8601..a267208987238 100644 --- a/library/std/src/io/buffered/bufreader/buffer.rs +++ b/library/alloc/src/io/buffered/bufreader/buffer.rs @@ -9,10 +9,13 @@ //! that user code which wants to do reads from a `BufReader` via `buffer` + `consume` can do so //! without encountering any runtime bounds checks. -use crate::cmp; +use core::cmp; +use core::mem::MaybeUninit; + +use crate::boxed::Box; use crate::io::{self, BorrowedBuf, ErrorKind, Read}; -use crate::mem::MaybeUninit; +#[expect(missing_debug_implementations)] pub struct Buffer { // The buffer. buf: Box<[MaybeUninit]>, @@ -29,12 +32,15 @@ pub struct Buffer { } impl Buffer { + #[cfg(not(no_global_oom_handling))] #[inline] pub fn with_capacity(capacity: usize) -> Self { let buf = Box::new_uninit_slice(capacity); Self { buf, pos: 0, filled: 0, initialized: false } } + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] #[inline] pub fn try_with_capacity(capacity: usize) -> io::Result { match Box::try_new_uninit_slice(capacity) { @@ -68,7 +74,8 @@ impl Buffer { } // This is only used by a test which asserts that the initialization-tracking is correct. - #[cfg(test)] + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub fn initialized(&self) -> bool { self.initialized } diff --git a/library/std/src/io/buffered/bufwriter.rs b/library/alloc/src/io/buffered/bufwriter.rs similarity index 95% rename from library/std/src/io/buffered/bufwriter.rs rename to library/alloc/src/io/buffered/bufwriter.rs index 1a5cc911c0e43..59fa1f230fd3c 100644 --- a/library/std/src/io/buffered/bufwriter.rs +++ b/library/alloc/src/io/buffered/bufwriter.rs @@ -1,8 +1,10 @@ +use core::mem::{self, ManuallyDrop}; +use core::{error, fmt, ptr}; + use crate::io::{ self, DEFAULT_BUF_SIZE, ErrorKind, IntoInnerError, IoSlice, Seek, SeekFrom, Write, }; -use crate::mem::{self, ManuallyDrop}; -use crate::{error, fmt, ptr}; +use crate::vec::Vec; /// Wraps a writer and buffers its output. /// @@ -60,8 +62,9 @@ use crate::{error, fmt, ptr}; /// together by the buffer and will all be written out in one system call when /// the `stream` is flushed. /// -/// [`TcpStream::write`]: crate::net::TcpStream::write -/// [`TcpStream`]: crate::net::TcpStream +// FIXME(#74481): Hard-links required to link from `alloc` to `std` +/// [`TcpStream::write`]: ../../std/net/struct.TcpStream.html#method.write +/// [`TcpStream`]: ../../std/net/struct.TcpStream.html /// [`flush`]: BufWriter::flush #[stable(feature = "rust1", since = "1.0.0")] pub struct BufWriter { @@ -87,20 +90,26 @@ impl BufWriter { /// use std::io::BufWriter; /// use std::net::TcpStream; /// + /// # #[expect(unused_mut)] /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn new(inner: W) -> BufWriter { BufWriter::with_capacity(DEFAULT_BUF_SIZE, inner) } - pub(crate) fn try_new_buffer() -> io::Result> { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn try_new_buffer() -> io::Result> { Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| { io::const_error!(ErrorKind::OutOfMemory, "failed to allocate write buffer") }) } - pub(crate) fn with_buffer(inner: W, buf: Vec) -> Self { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn with_buffer(inner: W, buf: Vec) -> Self { Self { inner, buf, panicked: false } } @@ -115,8 +124,10 @@ impl BufWriter { /// use std::net::TcpStream; /// /// let stream = TcpStream::connect("127.0.0.1:34254").unwrap(); + /// # #[expect(unused_mut)] /// let mut buffer = BufWriter::with_capacity(100, stream); /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(capacity: usize, inner: W) -> BufWriter { BufWriter { inner, buf: Vec::with_capacity(capacity), panicked: false } @@ -136,6 +147,7 @@ impl BufWriter { /// use std::io::BufWriter; /// use std::net::TcpStream; /// + /// # #[expect(unused_mut)] /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); /// /// // unwrap the TcpStream and flush the buffer @@ -192,7 +204,9 @@ impl BufWriter { /// "successfully written" (by returning nonzero success values from /// `write`), any 0-length writes from `inner` must be reported as i/o /// errors from this method. - pub(in crate::io) fn flush_buf(&mut self) -> io::Result<()> { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn flush_buf(&mut self) -> io::Result<()> { // SAFETY: `::copy_from` assumes that // this will not de-initialize any elements of `self.buf`'s spare // capacity. @@ -287,6 +301,7 @@ impl BufWriter { /// use std::io::BufWriter; /// use std::net::TcpStream; /// + /// # #[expect(unused_mut)] /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); /// /// // we can use reference just like buffer @@ -343,7 +358,9 @@ impl BufWriter { /// That the buffer is a `Vec` is an implementation detail. /// Callers should not modify the capacity as there currently is no public API to do so /// and thus any capacity changes would be unexpected by the user. - pub(in crate::io) fn buffer_mut(&mut self) -> &mut Vec { + #[doc(hidden)] + #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + pub fn buffer_mut(&mut self) -> &mut Vec { &mut self.buf } diff --git a/library/std/src/io/buffered/linewriter.rs b/library/alloc/src/io/buffered/linewriter.rs similarity index 98% rename from library/std/src/io/buffered/linewriter.rs rename to library/alloc/src/io/buffered/linewriter.rs index cc6921b86dd0b..bdb979e931f2e 100644 --- a/library/std/src/io/buffered/linewriter.rs +++ b/library/alloc/src/io/buffered/linewriter.rs @@ -84,6 +84,7 @@ impl LineWriter { /// Ok(()) /// } /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn new(inner: W) -> LineWriter { // Lines typically aren't that long, don't use a giant buffer @@ -105,6 +106,7 @@ impl LineWriter { /// Ok(()) /// } /// ``` + #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(capacity: usize, inner: W) -> LineWriter { LineWriter { inner: BufWriter::with_capacity(capacity, inner) } diff --git a/library/std/src/io/buffered/linewritershim.rs b/library/alloc/src/io/buffered/linewritershim.rs similarity index 99% rename from library/std/src/io/buffered/linewritershim.rs rename to library/alloc/src/io/buffered/linewritershim.rs index fc250e6ab7de9..35f448fc3aa8a 100644 --- a/library/std/src/io/buffered/linewritershim.rs +++ b/library/alloc/src/io/buffered/linewritershim.rs @@ -13,12 +13,12 @@ use crate::io::{self, BufWriter, IoSlice, Write}; /// `BufWriters` to be temporarily given line-buffering logic; this is what /// enables Stdout to be alternately in line-buffered or block-buffered mode. #[derive(Debug)] -pub struct LineWriterShim<'a, W: ?Sized + Write> { +pub(super) struct LineWriterShim<'a, W: ?Sized + Write> { buffer: &'a mut BufWriter, } impl<'a, W: ?Sized + Write> LineWriterShim<'a, W> { - pub fn new(buffer: &'a mut BufWriter) -> Self { + pub(super) fn new(buffer: &'a mut BufWriter) -> Self { Self { buffer } } diff --git a/library/alloc/src/io/buffered/mod.rs b/library/alloc/src/io/buffered/mod.rs new file mode 100644 index 0000000000000..1bddcfb801932 --- /dev/null +++ b/library/alloc/src/io/buffered/mod.rs @@ -0,0 +1,189 @@ +//! Buffering wrappers for I/O traits + +mod bufreader; +mod bufwriter; +mod linewriter; +mod linewritershim; + +use core::{error, fmt}; + +#[stable(feature = "bufwriter_into_parts", since = "1.56.0")] +pub use self::bufwriter::WriterPanicked; +use self::linewritershim::LineWriterShim; +#[stable(feature = "rust1", since = "1.0.0")] +pub use self::{bufreader::BufReader, bufwriter::BufWriter, linewriter::LineWriter}; +use crate::io::Error; + +/// An error returned by [`BufWriter::into_inner`] which combines an error that +/// happened while writing out the buffer, and the buffered writer object +/// which may be used to recover from the condition. +/// +/// # Examples +/// +/// ```no_run +/// use std::io::BufWriter; +/// use std::net::TcpStream; +/// +/// # #[expect(unused_mut)] +/// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); +/// +/// // do stuff with the stream +/// +/// // we want to get our `TcpStream` back, so let's try: +/// +/// let stream = match stream.into_inner() { +/// Ok(s) => s, +/// Err(e) => { +/// // Here, e is an IntoInnerError +/// panic!("An error occurred"); +/// } +/// }; +/// ``` +#[derive(Debug)] +#[stable(feature = "rust1", since = "1.0.0")] +pub struct IntoInnerError(W, Error); + +impl IntoInnerError { + /// Constructs a new IntoInnerError + fn new(writer: W, error: Error) -> Self { + Self(writer, error) + } + + /// Helper to construct a new IntoInnerError; intended to help with + /// adapters that wrap other adapters + fn new_wrapped(self, f: impl FnOnce(W) -> W2) -> IntoInnerError { + let Self(writer, error) = self; + IntoInnerError::new(f(writer), error) + } + + /// Returns the error which caused the call to [`BufWriter::into_inner()`] + /// to fail. + /// + /// This error was returned when attempting to write the internal buffer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// # #[expect(unused_mut)] + /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// + /// // do stuff with the stream + /// + /// // we want to get our `TcpStream` back, so let's try: + /// + /// let stream = match stream.into_inner() { + /// Ok(s) => s, + /// Err(e) => { + /// // Here, e is an IntoInnerError, let's log the inner error. + /// // + /// // We'll just 'log' to stdout for this example. + /// println!("{}", e.error()); + /// + /// panic!("An unexpected error occurred."); + /// } + /// }; + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + pub fn error(&self) -> &Error { + &self.1 + } + + /// Returns the buffered writer instance which generated the error. + /// + /// The returned object can be used for error recovery, such as + /// re-inspecting the buffer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// # #[expect(unused_mut)] + /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// + /// // do stuff with the stream + /// + /// // we want to get our `TcpStream` back, so let's try: + /// + /// let stream = match stream.into_inner() { + /// Ok(s) => s, + /// Err(e) => { + /// // Here, e is an IntoInnerError, let's re-examine the buffer: + /// let buffer = e.into_inner(); + /// + /// // do stuff to try to recover + /// + /// // afterwards, let's just return the stream + /// buffer.into_inner().unwrap() + /// } + /// }; + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + pub fn into_inner(self) -> W { + self.0 + } + + /// Consumes the [`IntoInnerError`] and returns the error which caused the call to + /// [`BufWriter::into_inner()`] to fail. Unlike `error`, this can be used to + /// obtain ownership of the underlying error. + /// + /// # Example + /// ``` + /// use std::io::{BufWriter, ErrorKind, Write}; + /// + /// let mut not_enough_space = [0u8; 10]; + /// let mut stream = BufWriter::new(not_enough_space.as_mut()); + /// write!(stream, "this cannot be actually written").unwrap(); + /// let into_inner_err = stream.into_inner().expect_err("now we discover it's too small"); + /// let err = into_inner_err.into_error(); + /// assert_eq!(err.kind(), ErrorKind::WriteZero); + /// ``` + #[stable(feature = "io_into_inner_error_parts", since = "1.55.0")] + pub fn into_error(self) -> Error { + self.1 + } + + /// Consumes the [`IntoInnerError`] and returns the error which caused the call to + /// [`BufWriter::into_inner()`] to fail, and the underlying writer. + /// + /// This can be used to simply obtain ownership of the underlying error; it can also be used for + /// advanced error recovery. + /// + /// # Example + /// ``` + /// use std::io::{BufWriter, ErrorKind, Write}; + /// + /// let mut not_enough_space = [0u8; 10]; + /// let mut stream = BufWriter::new(not_enough_space.as_mut()); + /// write!(stream, "this cannot be actually written").unwrap(); + /// let into_inner_err = stream.into_inner().expect_err("now we discover it's too small"); + /// let (err, recovered_writer) = into_inner_err.into_parts(); + /// assert_eq!(err.kind(), ErrorKind::WriteZero); + /// assert_eq!(recovered_writer.buffer(), b"t be actually written"); + /// ``` + #[stable(feature = "io_into_inner_error_parts", since = "1.55.0")] + pub fn into_parts(self) -> (Error, W) { + (self.1, self.0) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl From> for Error { + fn from(iie: IntoInnerError) -> Error { + iie.1 + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl error::Error for IntoInnerError {} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Display for IntoInnerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.error().fmt(f) + } +} diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index ff39cb145a929..dd2fffe8429f9 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -1,6 +1,7 @@ //! Traits, helpers, and type definitions for core I/O functionality. mod buf_read; +mod buffered; mod cursor; mod error; mod impls; @@ -31,6 +32,7 @@ pub use core::io::{ #[unstable(feature = "alloc_io", issue = "154046")] pub use self::{ buf_read::BufRead, + buffered::{BufReader, BufWriter, IntoInnerError, LineWriter, WriterPanicked}, read::{Read, read_to_string}, util::{Bytes, Lines, Split}, }; diff --git a/library/std/src/io/buffered/mod.rs b/library/std/src/io/buffered/mod.rs index e36f2d92108d0..1d09ff7d8dc1c 100644 --- a/library/std/src/io/buffered/mod.rs +++ b/library/std/src/io/buffered/mod.rs @@ -1,189 +1,4 @@ //! Buffering wrappers for I/O traits -mod bufreader; -mod bufwriter; -mod linewriter; -mod linewritershim; - #[cfg(test)] mod tests; - -#[stable(feature = "bufwriter_into_parts", since = "1.56.0")] -pub use bufwriter::WriterPanicked; -use linewritershim::LineWriterShim; - -#[stable(feature = "rust1", since = "1.0.0")] -pub use self::{bufreader::BufReader, bufwriter::BufWriter, linewriter::LineWriter}; -use crate::io::Error; -use crate::{error, fmt}; - -/// An error returned by [`BufWriter::into_inner`] which combines an error that -/// happened while writing out the buffer, and the buffered writer object -/// which may be used to recover from the condition. -/// -/// # Examples -/// -/// ```no_run -/// use std::io::BufWriter; -/// use std::net::TcpStream; -/// -/// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); -/// -/// // do stuff with the stream -/// -/// // we want to get our `TcpStream` back, so let's try: -/// -/// let stream = match stream.into_inner() { -/// Ok(s) => s, -/// Err(e) => { -/// // Here, e is an IntoInnerError -/// panic!("An error occurred"); -/// } -/// }; -/// ``` -#[derive(Debug)] -#[stable(feature = "rust1", since = "1.0.0")] -pub struct IntoInnerError(W, Error); - -impl IntoInnerError { - /// Constructs a new IntoInnerError - fn new(writer: W, error: Error) -> Self { - Self(writer, error) - } - - /// Helper to construct a new IntoInnerError; intended to help with - /// adapters that wrap other adapters - fn new_wrapped(self, f: impl FnOnce(W) -> W2) -> IntoInnerError { - let Self(writer, error) = self; - IntoInnerError::new(f(writer), error) - } - - /// Returns the error which caused the call to [`BufWriter::into_inner()`] - /// to fail. - /// - /// This error was returned when attempting to write the internal buffer. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::BufWriter; - /// use std::net::TcpStream; - /// - /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); - /// - /// // do stuff with the stream - /// - /// // we want to get our `TcpStream` back, so let's try: - /// - /// let stream = match stream.into_inner() { - /// Ok(s) => s, - /// Err(e) => { - /// // Here, e is an IntoInnerError, let's log the inner error. - /// // - /// // We'll just 'log' to stdout for this example. - /// println!("{}", e.error()); - /// - /// panic!("An unexpected error occurred."); - /// } - /// }; - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - pub fn error(&self) -> &Error { - &self.1 - } - - /// Returns the buffered writer instance which generated the error. - /// - /// The returned object can be used for error recovery, such as - /// re-inspecting the buffer. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::BufWriter; - /// use std::net::TcpStream; - /// - /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); - /// - /// // do stuff with the stream - /// - /// // we want to get our `TcpStream` back, so let's try: - /// - /// let stream = match stream.into_inner() { - /// Ok(s) => s, - /// Err(e) => { - /// // Here, e is an IntoInnerError, let's re-examine the buffer: - /// let buffer = e.into_inner(); - /// - /// // do stuff to try to recover - /// - /// // afterwards, let's just return the stream - /// buffer.into_inner().unwrap() - /// } - /// }; - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - pub fn into_inner(self) -> W { - self.0 - } - - /// Consumes the [`IntoInnerError`] and returns the error which caused the call to - /// [`BufWriter::into_inner()`] to fail. Unlike `error`, this can be used to - /// obtain ownership of the underlying error. - /// - /// # Example - /// ``` - /// use std::io::{BufWriter, ErrorKind, Write}; - /// - /// let mut not_enough_space = [0u8; 10]; - /// let mut stream = BufWriter::new(not_enough_space.as_mut()); - /// write!(stream, "this cannot be actually written").unwrap(); - /// let into_inner_err = stream.into_inner().expect_err("now we discover it's too small"); - /// let err = into_inner_err.into_error(); - /// assert_eq!(err.kind(), ErrorKind::WriteZero); - /// ``` - #[stable(feature = "io_into_inner_error_parts", since = "1.55.0")] - pub fn into_error(self) -> Error { - self.1 - } - - /// Consumes the [`IntoInnerError`] and returns the error which caused the call to - /// [`BufWriter::into_inner()`] to fail, and the underlying writer. - /// - /// This can be used to simply obtain ownership of the underlying error; it can also be used for - /// advanced error recovery. - /// - /// # Example - /// ``` - /// use std::io::{BufWriter, ErrorKind, Write}; - /// - /// let mut not_enough_space = [0u8; 10]; - /// let mut stream = BufWriter::new(not_enough_space.as_mut()); - /// write!(stream, "this cannot be actually written").unwrap(); - /// let into_inner_err = stream.into_inner().expect_err("now we discover it's too small"); - /// let (err, recovered_writer) = into_inner_err.into_parts(); - /// assert_eq!(err.kind(), ErrorKind::WriteZero); - /// assert_eq!(recovered_writer.buffer(), b"t be actually written"); - /// ``` - #[stable(feature = "io_into_inner_error_parts", since = "1.55.0")] - pub fn into_parts(self) -> (Error, W) { - (self.1, self.0) - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl From> for Error { - fn from(iie: IntoInnerError) -> Error { - iie.1 - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl error::Error for IntoInnerError {} - -#[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Display for IntoInnerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.error().fmt(f) - } -} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index dbc92d7b45d7c..572f0fb3e4611 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -297,11 +297,14 @@ #[cfg(test)] mod tests; +use alloc_crate::io::OsFunctions; #[unstable(feature = "raw_os_error_ty", issue = "107792")] pub use alloc_crate::io::RawOsError; #[doc(hidden)] #[unstable(feature = "io_const_error_internals", issue = "none")] pub use alloc_crate::io::SimpleMessage; +#[stable(feature = "bufwriter_into_parts", since = "1.56.0")] +pub use alloc_crate::io::WriterPanicked; #[unstable(feature = "io_const_error", issue = "133448")] pub use alloc_crate::io::const_error; #[stable(feature = "io_read_to_string", since = "1.65.0")] @@ -310,23 +313,20 @@ pub use alloc_crate::io::read_to_string; pub use alloc_crate::io::{BorrowedBuf, BorrowedCursor}; #[stable(feature = "rust1", since = "1.0.0")] pub use alloc_crate::io::{ - BufRead, Bytes, Chain, Cursor, Empty, Error, ErrorKind, Lines, Read, Repeat, Result, Seek, - SeekFrom, Sink, Split, Take, Write, empty, repeat, sink, + BufRead, BufReader, BufWriter, Bytes, Chain, Cursor, Empty, Error, ErrorKind, IntoInnerError, + LineWriter, Lines, Read, Repeat, Result, Seek, SeekFrom, Sink, Split, Take, Write, empty, + repeat, sink, }; #[allow(unused_imports, reason = "only used by certain platforms")] pub(crate) use alloc_crate::io::{ DEFAULT_BUF_SIZE, default_read_buf, default_read_vectored, default_write_vectored, }; pub(crate) use alloc_crate::io::{ - IoHandle, SpecReadByte, append_to_string, default_read_buf_exact, default_read_exact, - default_read_to_end, default_read_to_string, stream_len_default, uninlined_slow_read_byte, + IoHandle, SpecReadByte, default_read_to_end, default_read_to_string, stream_len_default, }; #[stable(feature = "iovec", since = "1.36.0")] pub use alloc_crate::io::{IoSlice, IoSliceMut}; -use alloc_crate::io::{OsFunctions, SizeHint}; -#[stable(feature = "bufwriter_into_parts", since = "1.56.0")] -pub use self::buffered::WriterPanicked; #[stable(feature = "anonymous_pipe", since = "1.87.0")] pub use self::pipe::{PipeReader, PipeWriter, pipe}; #[stable(feature = "is_terminal", since = "1.70.0")] @@ -340,7 +340,6 @@ pub use self::stdio::{_eprint, _print}; pub use self::stdio::{set_output_capture, try_set_output_capture}; #[stable(feature = "rust1", since = "1.0.0")] pub use self::{ - buffered::{BufReader, BufWriter, IntoInnerError, LineWriter}, copy::copy, stdio::{Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, stderr, stdin, stdout}, }; diff --git a/tests/ui/suggestions/mut-borrow-needed-by-trait.stderr b/tests/ui/suggestions/mut-borrow-needed-by-trait.stderr index eadf512a63b49..5ba7fcb2479bb 100644 --- a/tests/ui/suggestions/mut-borrow-needed-by-trait.stderr +++ b/tests/ui/suggestions/mut-borrow-needed-by-trait.stderr @@ -8,7 +8,7 @@ LL | let fp = BufWriter::new(fp); | = note: `std::io::Write` is implemented for `&mut dyn std::io::Write`, but not for `&dyn std::io::Write` note: required by a bound in `BufWriter::::new` - --> $SRC_DIR/std/src/io/buffered/bufwriter.rs:LL:COL + --> $SRC_DIR/alloc/src/io/buffered/bufwriter.rs:LL:COL error[E0277]: the trait bound `&dyn std::io::Write: std::io::Write` is not satisfied --> $DIR/mut-borrow-needed-by-trait.rs:17:14 @@ -18,7 +18,7 @@ LL | let fp = BufWriter::new(fp); | = note: `std::io::Write` is implemented for `&mut dyn std::io::Write`, but not for `&dyn std::io::Write` note: required by a bound in `BufWriter` - --> $SRC_DIR/std/src/io/buffered/bufwriter.rs:LL:COL + --> $SRC_DIR/alloc/src/io/buffered/bufwriter.rs:LL:COL error[E0599]: the method `write_fmt` exists for struct `BufWriter<&dyn std::io::Write>`, but its trait bounds were not satisfied --> $DIR/mut-borrow-needed-by-trait.rs:21:14 diff --git a/tests/ui/suggestions/suggest-change-mut.stderr b/tests/ui/suggestions/suggest-change-mut.stderr index c47ae433ab896..fc664c92ceee2 100644 --- a/tests/ui/suggestions/suggest-change-mut.stderr +++ b/tests/ui/suggestions/suggest-change-mut.stderr @@ -7,7 +7,7 @@ LL | let mut stream_reader = BufReader::new(&stream); | required by a bound introduced by this call | note: required by a bound in `BufReader::::new` - --> $SRC_DIR/std/src/io/buffered/bufreader.rs:LL:COL + --> $SRC_DIR/alloc/src/io/buffered/bufreader.rs:LL:COL help: consider removing the leading `&`-reference | LL - let mut stream_reader = BufReader::new(&stream); From f415df6e727215b61153774c5866bfc0a1772d6d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 26 May 2026 09:16:54 +1000 Subject: [PATCH 13/31] Reduce API surface of `Buffer` None of the currently public methods are accessible outside `std`, and are unused within. Therefore, they can be restricted to internal use. --- .../alloc/src/io/buffered/bufreader/buffer.rs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/library/alloc/src/io/buffered/bufreader/buffer.rs b/library/alloc/src/io/buffered/bufreader/buffer.rs index a267208987238..3c88e705c7011 100644 --- a/library/alloc/src/io/buffered/bufreader/buffer.rs +++ b/library/alloc/src/io/buffered/bufreader/buffer.rs @@ -15,7 +15,9 @@ use core::mem::MaybeUninit; use crate::boxed::Box; use crate::io::{self, BorrowedBuf, ErrorKind, Read}; -#[expect(missing_debug_implementations)] +#[doc(hidden)] +#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] +#[derive(Debug)] pub struct Buffer { // The buffer. buf: Box<[MaybeUninit]>, @@ -34,15 +36,13 @@ pub struct Buffer { impl Buffer { #[cfg(not(no_global_oom_handling))] #[inline] - pub fn with_capacity(capacity: usize) -> Self { + pub(super) fn with_capacity(capacity: usize) -> Self { let buf = Box::new_uninit_slice(capacity); Self { buf, pos: 0, filled: 0, initialized: false } } - #[doc(hidden)] - #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] #[inline] - pub fn try_with_capacity(capacity: usize) -> io::Result { + pub(super) fn try_with_capacity(capacity: usize) -> io::Result { match Box::try_new_uninit_slice(capacity) { Ok(buf) => Ok(Self { buf, pos: 0, filled: 0, initialized: false }), Err(_) => { @@ -52,49 +52,47 @@ impl Buffer { } #[inline] - pub fn buffer(&self) -> &[u8] { + pub(super) fn buffer(&self) -> &[u8] { // SAFETY: self.pos and self.filled are valid, and self.filled >= self.pos, and // that region is initialized because those are all invariants of this type. unsafe { self.buf.get_unchecked(self.pos..self.filled).assume_init_ref() } } #[inline] - pub fn capacity(&self) -> usize { + pub(super) fn capacity(&self) -> usize { self.buf.len() } #[inline] - pub fn filled(&self) -> usize { + pub(super) fn filled(&self) -> usize { self.filled } #[inline] - pub fn pos(&self) -> usize { + pub(super) fn pos(&self) -> usize { self.pos } // This is only used by a test which asserts that the initialization-tracking is correct. - #[doc(hidden)] - #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn initialized(&self) -> bool { + pub(super) fn initialized(&self) -> bool { self.initialized } #[inline] - pub fn discard_buffer(&mut self) { + pub(super) fn discard_buffer(&mut self) { self.pos = 0; self.filled = 0; } #[inline] - pub fn consume(&mut self, amt: usize) { + pub(super) fn consume(&mut self, amt: usize) { self.pos = cmp::min(self.pos + amt, self.filled); } /// If there are `amt` bytes available in the buffer, pass a slice containing those bytes to /// `visitor` and return true. If there are not enough bytes available, return false. #[inline] - pub fn consume_with(&mut self, amt: usize, mut visitor: V) -> bool + pub(super) fn consume_with(&mut self, amt: usize, mut visitor: V) -> bool where V: FnMut(&[u8]), { @@ -109,12 +107,12 @@ impl Buffer { } #[inline] - pub fn unconsume(&mut self, amt: usize) { + pub(super) fn unconsume(&mut self, amt: usize) { self.pos = self.pos.saturating_sub(amt); } /// Read more bytes into the buffer without discarding any of its contents - pub fn read_more(&mut self, mut reader: impl Read) -> io::Result { + pub(super) fn read_more(&mut self, mut reader: impl Read) -> io::Result { let mut buf = BorrowedBuf::from(&mut self.buf[self.filled..]); if self.initialized { @@ -131,14 +129,14 @@ impl Buffer { } /// Remove bytes that have already been read from the buffer. - pub fn backshift(&mut self) { + pub(super) fn backshift(&mut self) { self.buf.copy_within(self.pos..self.filled, 0); self.filled -= self.pos; self.pos = 0; } #[inline] - pub fn fill_buf(&mut self, mut reader: impl Read) -> io::Result<&[u8]> { + pub(super) fn fill_buf(&mut self, mut reader: impl Read) -> io::Result<&[u8]> { // If we've reached the end of our internal buffer then we need to fetch // some more data from the reader. // Branch using `>=` instead of the more correct `==` From 4af788e0ba851878b759ce55d83481c2f758854b Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 19 Jul 2026 20:40:16 +1000 Subject: [PATCH 14/31] Reduce API surface of `alloc::io::util` --- library/alloc/src/io/mod.rs | 3 ++- library/alloc/src/io/util.rs | 16 ++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index dd2fffe8429f9..ad702c90afa69 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -29,6 +29,7 @@ pub use core::io::{ stream_len_default, take, }; +use self::util::{bytes, lines, split, uninlined_slow_read_byte}; #[unstable(feature = "alloc_io", issue = "154046")] pub use self::{ buf_read::BufRead, @@ -43,5 +44,5 @@ pub use self::{ DEFAULT_BUF_SIZE, append_to_string, default_read_buf, default_read_buf_exact, default_read_exact, default_read_to_end, default_read_to_string, default_read_vectored, }, - util::{SpecReadByte, bytes, lines, split, uninlined_slow_read_byte}, + util::SpecReadByte, }; diff --git a/library/alloc/src/io/util.rs b/library/alloc/src/io/util.rs index 050e018e8568f..567a041be5b78 100644 --- a/library/alloc/src/io/util.rs +++ b/library/alloc/src/io/util.rs @@ -388,15 +388,11 @@ fn inlined_slow_read_byte(reader: &mut R) -> Option> { // Used by `BufReader::spec_read_byte`, for which the `inline(never)` is // important. #[inline(never)] -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn uninlined_slow_read_byte(reader: &mut R) -> Option> { +pub(in crate::io) fn uninlined_slow_read_byte(reader: &mut R) -> Option> { inlined_slow_read_byte(reader) } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub const fn bytes(inner: R) -> Bytes { +pub(in crate::io) const fn bytes(inner: R) -> Bytes { Bytes { inner } } @@ -434,9 +430,7 @@ impl Iterator for Split { } } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub const fn split(buf: B, delim: u8) -> Split { +pub(in crate::io) const fn split(buf: B, delim: u8) -> Split { Split { buf, delim } } @@ -475,8 +469,6 @@ impl Iterator for Lines { } } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub const fn lines(buf: B) -> Lines { +pub(in crate::io) const fn lines(buf: B) -> Lines { Lines { buf } } From 4f8a2a924511ad7180010c8eee5bcfc7861fcdc8 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 19 Jul 2026 20:41:02 +1000 Subject: [PATCH 15/31] Reduce API surface of `alloc::io::read` --- library/alloc/src/io/mod.rs | 5 +++-- library/alloc/src/io/read.rs | 15 ++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index ad702c90afa69..75bbd2f0adc1b 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -29,6 +29,7 @@ pub use core::io::{ stream_len_default, take, }; +use self::read::{append_to_string, default_read_buf_exact, default_read_exact}; use self::util::{bytes, lines, split, uninlined_slow_read_byte}; #[unstable(feature = "alloc_io", issue = "154046")] pub use self::{ @@ -41,8 +42,8 @@ pub use self::{ #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] pub use self::{ read::{ - DEFAULT_BUF_SIZE, append_to_string, default_read_buf, default_read_buf_exact, - default_read_exact, default_read_to_end, default_read_to_string, default_read_vectored, + DEFAULT_BUF_SIZE, default_read_buf, default_read_to_end, default_read_to_string, + default_read_vectored, }, util::SpecReadByte, }; diff --git a/library/alloc/src/io/read.rs b/library/alloc/src/io/read.rs index c1356811cc58a..1b47c20b01962 100644 --- a/library/alloc/src/io/read.rs +++ b/library/alloc/src/io/read.rs @@ -795,9 +795,7 @@ pub const DEFAULT_BUF_SIZE: usize = cfg_select! { /// 2. We're passing a raw buffer to the function `f`, and it is expected that /// the function only *appends* bytes to the buffer. We'll get undefined /// behavior if existing bytes are overwritten to have non-UTF-8 data. -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub unsafe fn append_to_string(buf: &mut String, f: F) -> Result +pub(in crate::io) unsafe fn append_to_string(buf: &mut String, f: F) -> Result where F: FnOnce(&mut Vec) -> Result, { @@ -988,9 +986,10 @@ where read(buf) } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<()> { +pub(in crate::io) fn default_read_exact( + this: &mut R, + mut buf: &mut [u8], +) -> Result<()> { while !buf.is_empty() { match this.read(buf) { Ok(0) => break, @@ -1015,9 +1014,7 @@ where Ok(()) } -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub fn default_read_buf_exact( +pub(in crate::io) fn default_read_buf_exact( this: &mut R, mut cursor: BorrowedCursor<'_, u8>, ) -> Result<()> { From 44d9de29f242057908355ffaa3ca56095bd7fea5 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 19 Jul 2026 20:42:02 +1000 Subject: [PATCH 16/31] Reduce API surface of `alloc::io` reexports from `core::io` --- library/alloc/src/io/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/alloc/src/io/mod.rs b/library/alloc/src/io/mod.rs index 75bbd2f0adc1b..ef09d13cc6247 100644 --- a/library/alloc/src/io/mod.rs +++ b/library/alloc/src/io/mod.rs @@ -23,10 +23,10 @@ pub use core::io::{ }; #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -pub use core::io::{ - IoHandle, OsFunctions, SizeHint, WriteThroughCursor, chain, default_write_vectored, - slice_write, slice_write_all, slice_write_all_vectored, slice_write_vectored, - stream_len_default, take, +pub use core::io::{IoHandle, OsFunctions, default_write_vectored, stream_len_default}; +use core::io::{ + SizeHint, WriteThroughCursor, chain, slice_write, slice_write_all, slice_write_all_vectored, + slice_write_vectored, take, }; use self::read::{append_to_string, default_read_buf_exact, default_read_exact}; From f012e40a8372d6d25b2139f707d28dea70d9d6e4 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 20 Jul 2026 10:29:29 +1000 Subject: [PATCH 17/31] Use `String::try_push_str` instead of `unsafe` code Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/buffered/bufreader.rs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/library/alloc/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs index 9a912be8e18f0..c6039e19b1796 100644 --- a/library/alloc/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -481,25 +481,10 @@ impl Read for BufReader { cfg_select! { no_global_oom_handling => { - // SAFETY: - // * string and buf are non-overlapping - // * buf[..len] is already initialized - // * buf[len..len + count] is initialized by copy_nonoverlapping - // * len + count is within the capacity of buf based on the reservation completed above - // * buf is appended with valid UTF-8 data and is initially valid UTF-8, therefore - // it is valid UTF-8 at all times. - unsafe { - let buf = buf.as_mut_vec(); - let count = string.len(); - let len = buf.len(); - let src = string.as_ptr(); - let dst = buf.as_mut_ptr().add(len); - core::ptr::copy_nonoverlapping(src, dst, count); - buf.set_len(len + count); - } + buf.try_push_str(string)?; } _ => { - *buf += string; + buf.push_str(string); } } From 295f7f464a42c4b3f23a5fed0afea1899b6ceb4a Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 20 Jul 2026 10:29:54 +1000 Subject: [PATCH 18/31] Use `Vec::::try_extend_from_slice_of_bytes` instead of `unsafe` code Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/buffered/bufreader.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/library/alloc/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs index c6039e19b1796..ad1a03412f72f 100644 --- a/library/alloc/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -428,19 +428,7 @@ impl Read for BufReader { cfg_select! { no_global_oom_handling => { - // SAFETY: - // * inner_buf and buf are non-overlapping - // * buf[..len] is already initialized - // * buf[len..len + count] is initialized by copy_nonoverlapping - // * len + count is within the capacity of buf based on the reservation completed above - unsafe { - let count = inner_buf.len(); - let len = buf.len(); - let src = inner_buf.as_ptr(); - let dst = buf.as_mut_ptr().add(len); - core::ptr::copy_nonoverlapping(src, dst, count); - buf.set_len(len + count); - } + buf.try_extend_from_slice_of_bytes(inner_buf)?; } _ => { buf.extend_from_slice(inner_buf); From 3739d0425b35c374e9476699224e600830fed5b5 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 20 Jul 2026 11:09:38 +1000 Subject: [PATCH 19/31] Make `Buffer` private --- library/alloc/src/io/buffered/bufreader.rs | 16 +++++++--------- .../alloc/src/io/buffered/bufreader/buffer.rs | 5 +---- library/alloc/src/io/buffered/bufwriter.rs | 16 +++++++--------- library/std/src/fs.rs | 8 ++------ 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/library/alloc/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs index ad1a03412f72f..12216c70059d6 100644 --- a/library/alloc/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -1,6 +1,6 @@ mod buffer; -pub(super) use buffer::Buffer; +use buffer::Buffer; use crate::fmt; use crate::io::{ @@ -79,16 +79,14 @@ impl BufReader { BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) } + /// Attempts to allocate an internal buffer, _then_ calls the provided function + /// to retrieve the inner reader `R`. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn try_new_buffer() -> io::Result { - Buffer::try_with_capacity(DEFAULT_BUF_SIZE) - } - - #[doc(hidden)] - #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn with_buffer(inner: R, buf: Buffer) -> Self { - Self { inner, buf } + pub fn try_new_with(f: impl FnOnce() -> io::Result) -> io::Result { + let buf = Buffer::try_with_capacity(DEFAULT_BUF_SIZE)?; + let inner = f()?; + Ok(Self { inner, buf }) } /// Creates a new `BufReader` with the specified buffer capacity. diff --git a/library/alloc/src/io/buffered/bufreader/buffer.rs b/library/alloc/src/io/buffered/bufreader/buffer.rs index 3c88e705c7011..316889d93f576 100644 --- a/library/alloc/src/io/buffered/bufreader/buffer.rs +++ b/library/alloc/src/io/buffered/bufreader/buffer.rs @@ -15,10 +15,7 @@ use core::mem::MaybeUninit; use crate::boxed::Box; use crate::io::{self, BorrowedBuf, ErrorKind, Read}; -#[doc(hidden)] -#[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] -#[derive(Debug)] -pub struct Buffer { +pub(super) struct Buffer { // The buffer. buf: Box<[MaybeUninit]>, // The current seek offset into `buf`, must always be <= `filled`. diff --git a/library/alloc/src/io/buffered/bufwriter.rs b/library/alloc/src/io/buffered/bufwriter.rs index 59fa1f230fd3c..d36c0ccca8e89 100644 --- a/library/alloc/src/io/buffered/bufwriter.rs +++ b/library/alloc/src/io/buffered/bufwriter.rs @@ -99,18 +99,16 @@ impl BufWriter { BufWriter::with_capacity(DEFAULT_BUF_SIZE, inner) } + /// Attempts to allocate an internal buffer, _then_ calls the provided function + /// to retrieve the inner writer `W`. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn try_new_buffer() -> io::Result> { - Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| { + pub fn try_new_with(f: impl FnOnce() -> io::Result) -> io::Result { + let buf = Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| { io::const_error!(ErrorKind::OutOfMemory, "failed to allocate write buffer") - }) - } - - #[doc(hidden)] - #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] - pub fn with_buffer(inner: W, buf: Vec) -> Self { - Self { inner, buf, panicked: false } + })?; + let inner = f()?; + Ok(Self { inner, buf, panicked: false }) } /// Creates a new `BufWriter` with at least the specified buffer capacity. diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 3574855e04dc9..388bf4a55e11f 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -604,9 +604,7 @@ impl File { #[unstable(feature = "file_buffered", issue = "130804")] pub fn open_buffered>(path: P) -> io::Result> { // Allocate the buffer *first* so we don't affect the filesystem otherwise. - let buffer = io::BufReader::::try_new_buffer()?; - let file = File::open(path)?; - Ok(io::BufReader::with_buffer(file, buffer)) + io::BufReader::try_new_with(|| File::open(path)) } /// Opens a file in write-only mode. @@ -672,9 +670,7 @@ impl File { #[unstable(feature = "file_buffered", issue = "130804")] pub fn create_buffered>(path: P) -> io::Result> { // Allocate the buffer *first* so we don't affect the filesystem otherwise. - let buffer = io::BufWriter::::try_new_buffer()?; - let file = File::create(path)?; - Ok(io::BufWriter::with_buffer(file, buffer)) + io::BufWriter::try_new_with(|| File::create(path)) } /// Creates a new file in read-write mode; error if the file exists. From 69b380676dc71c90a5f6122d1ab41ee101b21247 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 20 Jul 2026 11:28:13 +1000 Subject: [PATCH 20/31] Standardize on `pub(super)` instead of `pub(in crate::io)` Co-Authored-By: Clar Fon <15850505+clarfonthey@users.noreply.github.com> --- library/alloc/src/io/read.rs | 9 +++------ library/alloc/src/io/util.rs | 8 ++++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/library/alloc/src/io/read.rs b/library/alloc/src/io/read.rs index 1b47c20b01962..c0123c860b453 100644 --- a/library/alloc/src/io/read.rs +++ b/library/alloc/src/io/read.rs @@ -795,7 +795,7 @@ pub const DEFAULT_BUF_SIZE: usize = cfg_select! { /// 2. We're passing a raw buffer to the function `f`, and it is expected that /// the function only *appends* bytes to the buffer. We'll get undefined /// behavior if existing bytes are overwritten to have non-UTF-8 data. -pub(in crate::io) unsafe fn append_to_string(buf: &mut String, f: F) -> Result +pub(super) unsafe fn append_to_string(buf: &mut String, f: F) -> Result where F: FnOnce(&mut Vec) -> Result, { @@ -986,10 +986,7 @@ where read(buf) } -pub(in crate::io) fn default_read_exact( - this: &mut R, - mut buf: &mut [u8], -) -> Result<()> { +pub(super) fn default_read_exact(this: &mut R, mut buf: &mut [u8]) -> Result<()> { while !buf.is_empty() { match this.read(buf) { Ok(0) => break, @@ -1014,7 +1011,7 @@ where Ok(()) } -pub(in crate::io) fn default_read_buf_exact( +pub(super) fn default_read_buf_exact( this: &mut R, mut cursor: BorrowedCursor<'_, u8>, ) -> Result<()> { diff --git a/library/alloc/src/io/util.rs b/library/alloc/src/io/util.rs index 567a041be5b78..9bf5a56dd7157 100644 --- a/library/alloc/src/io/util.rs +++ b/library/alloc/src/io/util.rs @@ -388,11 +388,11 @@ fn inlined_slow_read_byte(reader: &mut R) -> Option> { // Used by `BufReader::spec_read_byte`, for which the `inline(never)` is // important. #[inline(never)] -pub(in crate::io) fn uninlined_slow_read_byte(reader: &mut R) -> Option> { +pub(super) fn uninlined_slow_read_byte(reader: &mut R) -> Option> { inlined_slow_read_byte(reader) } -pub(in crate::io) const fn bytes(inner: R) -> Bytes { +pub(super) const fn bytes(inner: R) -> Bytes { Bytes { inner } } @@ -430,7 +430,7 @@ impl Iterator for Split { } } -pub(in crate::io) const fn split(buf: B, delim: u8) -> Split { +pub(super) const fn split(buf: B, delim: u8) -> Split { Split { buf, delim } } @@ -469,6 +469,6 @@ impl Iterator for Lines { } } -pub(in crate::io) const fn lines(buf: B) -> Lines { +pub(super) const fn lines(buf: B) -> Lines { Lines { buf } } From e09b8878215c40710f7218a7eaefeffe7ee6486e Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 20 Jul 2026 10:49:36 +0200 Subject: [PATCH 21/31] fix feature gate --- library/portable-simd/crates/std_float/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/library/portable-simd/crates/std_float/src/lib.rs b/library/portable-simd/crates/std_float/src/lib.rs index 4714881bcdad3..d9499e539365f 100644 --- a/library/portable-simd/crates/std_float/src/lib.rs +++ b/library/portable-simd/crates/std_float/src/lib.rs @@ -1,18 +1,17 @@ -#![feature(impl_restriction)] #![cfg_attr( feature = "as_crate", feature(core_intrinsics), feature(portable_simd), feature(f16), + feature(impl_restriction), allow(internal_features) )] +use core::intrinsics::simd as intrinsics; #[cfg(not(feature = "as_crate"))] use core::simd; + #[cfg(feature = "as_crate")] use core_simd::simd; - -use core::intrinsics::simd as intrinsics; - use simd::Simd; /// This trait provides a possibly-temporary implementation of float functions From b045903e32d5650c6713f4030e81a1703ff03614 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 20 Jul 2026 10:49:53 +0200 Subject: [PATCH 22/31] remove now-dead `Sealed` trait --- library/std/src/lib.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index a2582355d6794..a912060a74ce2 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -808,25 +808,6 @@ include!("../../core/src/primitive_docs.rs"); #[unstable(feature = "restricted_std", issue = "none")] mod __restricted_std_workaround {} -// FIXME(jhpratt) This is currently only used by portable SIMD. Once rust-lang/portable-simd#529 is -// merged, this should be able to be removed. -mod sealed { - /// This trait being unreachable from outside the crate - /// prevents outside implementations of our extension traits. - /// This allows adding more trait methods in the future. - #[unstable(feature = "sealed", issue = "none")] - pub trait Sealed {} -} - -macro_rules! impl_sealed { - ($($t:ty)*) => {$( - /// Allows implementations within `std`. - #[unstable(feature = "sealed", issue = "none")] - impl crate::sealed::Sealed for $t {} - )*} -} -impl_sealed! { isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 f32 f64 } - #[cfg(test)] #[allow(dead_code)] // Not used in all configurations. pub(crate) mod test_helpers; From 76298612b0d72a6a664ec50a4fd3af496a5c2160 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 20 Jul 2026 12:17:33 +0200 Subject: [PATCH 23/31] fix simd tests that use `without_provenance{_mut}` --- src/tools/miri/tests/pass/intrinsics/portable-simd-ptrs.rs | 2 +- ...r.enumerated_loop.runtime-optimized.after.panic-unwind.mir | 2 +- ...iter.forward_loop.runtime-optimized.after.panic-unwind.mir | 4 ++-- ...iter.reverse_loop.runtime-optimized.after.panic-unwind.mir | 2 +- ...r.slice_iter_next.runtime-optimized.after.panic-unwind.mir | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tools/miri/tests/pass/intrinsics/portable-simd-ptrs.rs b/src/tools/miri/tests/pass/intrinsics/portable-simd-ptrs.rs index 096ec78da1a0e..df5cdaacfa869 100644 --- a/src/tools/miri/tests/pass/intrinsics/portable-simd-ptrs.rs +++ b/src/tools/miri/tests/pass/intrinsics/portable-simd-ptrs.rs @@ -8,5 +8,5 @@ fn main() { // Pointer casts let _val: Simd<*const u8, 4> = Simd::<*const i32, 4>::splat(ptr::null()).cast(); let addrs = Simd::<*const i32, 4>::splat(ptr::null()).expose_provenance(); - let _ptrs = Simd::<*const i32, 4>::with_exposed_provenance(addrs); + let _ptrs: Simd<*const i32, 4> = std::simd::ptr::with_exposed_provenance(addrs); } diff --git a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-unwind.mir index 034416c173b49..bdb72abeac7e4 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-unwind.mir @@ -35,7 +35,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () { scope 7 { } scope 11 (inlined std::ptr::without_provenance::) { - scope 12 (inlined without_provenance_mut::) { + scope 12 (inlined std::ptr::without_provenance_mut::) { } } scope 13 (inlined NonNull::::as_ptr) { diff --git a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.runtime-optimized.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.runtime-optimized.after.panic-unwind.mir index 3bcd93396f614..dc5feaa1344b0 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.runtime-optimized.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.runtime-optimized.after.panic-unwind.mir @@ -34,7 +34,7 @@ fn forward_loop(_1: &[T], _2: impl Fn(&T)) -> () { } } } - scope 25 (inlined without_provenance_mut::) { + scope 25 (inlined std::ptr::without_provenance_mut::) { } } scope 20 (inlined std::ptr::const_ptr::::addr) { @@ -77,7 +77,7 @@ fn forward_loop(_1: &[T], _2: impl Fn(&T)) -> () { scope 7 { } scope 11 (inlined std::ptr::without_provenance::) { - scope 12 (inlined without_provenance_mut::) { + scope 12 (inlined std::ptr::without_provenance_mut::) { } } scope 13 (inlined NonNull::::as_ptr) { diff --git a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.runtime-optimized.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.runtime-optimized.after.panic-unwind.mir index d0b125be28d4f..c11eab18521dd 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.runtime-optimized.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.runtime-optimized.after.panic-unwind.mir @@ -103,7 +103,7 @@ fn reverse_loop(_1: &[T], _2: impl Fn(&T)) -> () { scope 7 { } scope 11 (inlined std::ptr::without_provenance::) { - scope 12 (inlined without_provenance_mut::) { + scope 12 (inlined std::ptr::without_provenance_mut::) { } } scope 13 (inlined NonNull::::as_ptr) { diff --git a/tests/mir-opt/pre-codegen/slice_iter.slice_iter_next.runtime-optimized.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.slice_iter_next.runtime-optimized.after.panic-unwind.mir index 7596384ac4a89..889d7c1e69afb 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.slice_iter_next.runtime-optimized.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.slice_iter_next.runtime-optimized.after.panic-unwind.mir @@ -21,7 +21,7 @@ fn slice_iter_next(_1: &mut std::slice::Iter<'_, T>) -> Option<&T> { } } } - scope 10 (inlined without_provenance_mut::) { + scope 10 (inlined std::ptr::without_provenance_mut::) { } } scope 5 (inlined std::ptr::const_ptr::::addr) { From 9d38fe5be7ad7e9b34972387be109b814b9c4007 Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Mon, 20 Jul 2026 22:16:52 +1000 Subject: [PATCH 24/31] Add `inline` to new `BufReader` method --- library/alloc/src/io/buffered/bufreader.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/alloc/src/io/buffered/bufreader.rs b/library/alloc/src/io/buffered/bufreader.rs index 12216c70059d6..e8b3302e29b98 100644 --- a/library/alloc/src/io/buffered/bufreader.rs +++ b/library/alloc/src/io/buffered/bufreader.rs @@ -83,6 +83,7 @@ impl BufReader { /// to retrieve the inner reader `R`. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + #[inline] pub fn try_new_with(f: impl FnOnce() -> io::Result) -> io::Result { let buf = Buffer::try_with_capacity(DEFAULT_BUF_SIZE)?; let inner = f()?; From 6ef1cf3cf0303352fb8a4528020dc28095003055 Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Mon, 20 Jul 2026 22:17:16 +1000 Subject: [PATCH 25/31] Add `inline` to new `BufWriter` method --- library/alloc/src/io/buffered/bufwriter.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/alloc/src/io/buffered/bufwriter.rs b/library/alloc/src/io/buffered/bufwriter.rs index d36c0ccca8e89..4847c1605ac79 100644 --- a/library/alloc/src/io/buffered/bufwriter.rs +++ b/library/alloc/src/io/buffered/bufwriter.rs @@ -103,6 +103,7 @@ impl BufWriter { /// to retrieve the inner writer `W`. #[doc(hidden)] #[unstable(feature = "core_io_internals", reason = "exposed only for libstd", issue = "none")] + #[inline] pub fn try_new_with(f: impl FnOnce() -> io::Result) -> io::Result { let buf = Vec::try_with_capacity(DEFAULT_BUF_SIZE).map_err(|_| { io::const_error!(ErrorKind::OutOfMemory, "failed to allocate write buffer") From f32f69b3c9fd6885c6e905071be99dded66da9e0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 20 Jul 2026 18:26:30 +0200 Subject: [PATCH 26/31] disable `portable_simd` miri test that needs additional intrinsic support --- src/tools/miri/tests/pass/intrinsics/portable-simd.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs index ac7838247f288..e507e92624bde 100644 --- a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs +++ b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs @@ -766,6 +766,11 @@ fn simd_swizzle() { } fn simd_swizzle_dyn() { + // FIXME: needs llvm.neon.tbl2 support, see https://github.com/rust-lang/miri/pull/5219. + if cfg!(target_arch = "aarch64") { + return; + } + fn check_swizzle_dyn() { assert_eq!( Simd::::default().swizzle_dyn(Simd::::default()), From 9204d143ba2871701d98c2e2de66e51a260edc3d Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Mon, 20 Jul 2026 16:43:11 +0000 Subject: [PATCH 27/31] ergonomic_clones_dotuse_capture_by_ref: Capture upvar by ref for `.use` in non-move closures * Capture upvar by ref for `.use` in non-move closures A plain `|| { x.use }` closure marked the upvar capture as `ByUse`, which clones the value into the closure at construction time. The `.use` in the body then clones again on every call, so the value was cloned once more than the number of invocations. A `ByUse` capture in a non-`move`/`use` closure can only originate from a `.use` expression in the body; a `use ||` capture clause is handled by `adjust_for_use_closure` instead. Capture such places by immutable borrow so the `.use` clones once per evaluation and nothing is cloned into the closure at construction. * Add test for clone count of `.use` in non-move closure --- compiler/rustc_hir_typeck/src/upvar.rs | 12 +++- .../closure/dotuse-no-extra-capture-clone.rs | 69 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/ui/ergonomic-clones/closure/dotuse-no-extra-capture-clone.rs diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 4a92a3ed62e0e..d75d346e712e0 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -2344,12 +2344,22 @@ fn adjust_for_non_move_closure( place.projections.iter().position(|proj| proj.kind == ProjectionKind::Deref); match kind { - ty::UpvarCapture::ByValue | ty::UpvarCapture::ByUse => { + ty::UpvarCapture::ByValue => { if let Some(idx) = contains_deref { truncate_place_to_len_and_update_capture_kind(&mut place, &mut kind, idx); } } + // A non-`move`/`use` closure that only `.use`s an upvar does not need to + // own (and thus clone-on-capture) the value. The `ByUse` kind here can only + // come from a `x.use` in the body (a `use ||` capture clause goes through + // `adjust_for_use_closure` instead). Capturing such a place by immutable + // borrow lets the `.use` expression clone per evaluation, rather than also + // cloning the value into the closure at construction time. See #157141. + ty::UpvarCapture::ByUse => { + kind = ty::UpvarCapture::ByRef(ty::BorrowKind::Immutable); + } + ty::UpvarCapture::ByRef(..) => {} } diff --git a/tests/ui/ergonomic-clones/closure/dotuse-no-extra-capture-clone.rs b/tests/ui/ergonomic-clones/closure/dotuse-no-extra-capture-clone.rs new file mode 100644 index 0000000000000..c1662d6f4d1b8 --- /dev/null +++ b/tests/ui/ergonomic-clones/closure/dotuse-no-extra-capture-clone.rs @@ -0,0 +1,69 @@ +//! Regression test for #157141. +//! +//! A non-`move`, non-`use` closure that only `.use`s an upvar should capture it +//! by immutable borrow, not by `use` (which would clone the value into the +//! closure at construction time). The `.use` expression in the body is then the +//! only thing that clones, once per evaluation. + +//@ run-pass + +#![feature(ergonomic_clones)] +#![allow(incomplete_features)] + +use std::sync::atomic::{AtomicUsize, Ordering}; + +static CLONES: AtomicUsize = AtomicUsize::new(0); + +struct Thing; + +impl Clone for Thing { + fn clone(&self) -> Self { + CLONES.fetch_add(1, Ordering::Relaxed); + Thing + } +} + +impl std::clone::UseCloned for Thing {} + +fn clones_during(f: impl FnOnce() -> R) -> usize { + let before = CLONES.load(Ordering::Relaxed); + f(); + CLONES.load(Ordering::Relaxed) - before +} + +fn main() { + // Plain `||` closure: `x` is borrowed, so building the closure clones nothing. + // Each call clones exactly once via the `.use` in the body. + let x = Thing; + let n = clones_during(|| { + let closure = || { + let _y = x.use; + }; + closure(); + closure(); + }); + assert_eq!(n, 2, "plain `||` closure should clone once per call, not also at capture"); + drop(x); + + // `move ||` closure: `x` is moved in (no capture clone), `.use` clones per call. + let x = Thing; + let n = clones_during(|| { + let closure = move || { + let _y = x.use; + }; + closure(); + closure(); + }); + assert_eq!(n, 2, "`move ||` closure should clone once per call"); + + // `use ||` closure: the capture clause clones `x` into the closure once, at + // construction. Calling it afterwards moves the captured value out. + let x = Thing; + let n = clones_during(|| { + let closure = use || { + let _y = &x; + }; + closure(); + }); + assert_eq!(n, 1, "`use ||` closure clones once at construction"); +} From 192ce21f9a25e6373e2891fd6db0cc70169d9cfc Mon Sep 17 00:00:00 2001 From: Obei Sideg Date: Sun, 28 Jun 2026 14:51:39 +0300 Subject: [PATCH 28/31] Make parsed attributes available in `finalize_check` the parsed attributes of an item were only fully populated after all finalizers had run, so `finalize_check` (run during finalization) could only inspect attribute *paths* via `FinalizeContext::all_attrs`, not the parsed `AttributeKind`s. split finalization into two passes: first run all finalizers to produce the parsed attributes, then run the cross-attribute checks. The checks are deferred via a new `AttributeParser::deferred_finalize_check`, and can now inspect the fully parsed attributes through a new `FinalizeContext::parsed_attrs` field. --- .../rustc_attr_parsing/src/attributes/mod.rs | 40 ++++++++++++++----- compiler/rustc_attr_parsing/src/context.rs | 36 +++++++++++++++-- compiler/rustc_attr_parsing/src/interface.rs | 38 ++++++++++++++++-- 3 files changed, 97 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index d3ddd79a97619..f1122779eecc0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -26,7 +26,7 @@ use rustc_span::edition::Edition; use rustc_span::{Span, Symbol}; use thin_vec::ThinVec; -use crate::context::{AcceptContext, FinalizeContext}; +use crate::context::{AcceptContext, FinalizeCheckFn, FinalizeContext}; use crate::parser::ArgParser; use crate::session_diagnostics::UnusedMultiple; use crate::target_checking::AllowedTargets; @@ -117,6 +117,20 @@ pub(crate) trait AttributeParser: Default + 'static { /// every single syntax item that could have attributes applied to it. /// Your accept mappings should determine whether this returns something. fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option; + + /// If this parser produced an attribute, optionally returns a cross-attribute check + /// to run once *all* attributes on the item have been finalized, together with the + /// span it should be reported at. + /// + /// Running after finalization means the check can inspect the fully parsed attributes + /// via [`FinalizeContext::parsed_attrs`], which are not yet all available during + /// [`finalize`](Self::finalize). This is queried right before `finalize` consumes the + /// parser state. + /// + /// Defaults to no check. + fn deferred_finalize_check(&self) -> Option<(FinalizeCheckFn, Span)> { + None + } } /// Alternative to [`AttributeParser`] that automatically handles state management. @@ -185,11 +199,15 @@ impl AttributeParser for Single { const ALLOWED_TARGETS: AllowedTargets<'_> = T::ALLOWED_TARGETS; const SAFETY: AttributeSafety = T::SAFETY; - fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { - let (kind, span) = self.1?; - T::finalize_check(cx, span); + fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option { + let (kind, _span) = self.1?; Some(kind) } + + fn deferred_finalize_check(&self) -> Option<(FinalizeCheckFn, Span)> { + let (_, span) = self.1.as_ref()?; + Some((::finalize_check, *span)) + } } pub(crate) enum OnDuplicate { @@ -375,12 +393,12 @@ impl AttributeParser for Combine { const ALLOWED_TARGETS: AllowedTargets<'_> = T::ALLOWED_TARGETS; const SAFETY: AttributeSafety = T::SAFETY; - fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { - if let Some(first_span) = self.first_span { - T::finalize_check(cx, first_span); - Some(T::CONVERT(self.items, first_span)) - } else { - None - } + fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option { + let first_span = self.first_span?; + Some(T::CONVERT(self.items, first_span)) + } + + fn deferred_finalize_check(&self) -> Option<(FinalizeCheckFn, Span)> { + Some((::finalize_check, self.first_span?)) } } diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index efa43562aeeca..dd8a77fb38c4f 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -12,8 +12,8 @@ use rustc_ast::{AttrStyle, MetaItemLit, Safety}; use rustc_data_structures::sync::{DynSend, DynSync}; use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, Level, MultiSpan}; use rustc_feature::AttributeStability; -use rustc_hir::AttrPath; use rustc_hir::attrs::AttributeKind; +use rustc_hir::{AttrPath, Attribute}; use rustc_parse::parser::Recovery; use rustc_session::Session; use rustc_session::lint::{Lint, LintId}; @@ -94,7 +94,24 @@ pub(super) struct GroupTypeInnerAccept { pub(crate) type AcceptFn = Box Fn(&mut AcceptContext<'_, 'sess>, &ArgParser) + Send + Sync>; -pub(crate) type FinalizeFn = fn(&mut FinalizeContext<'_, '_>) -> Option; + +pub(crate) type FinalizeFn = fn(&mut FinalizeContext<'_, '_>) -> FinalizeOutput; + +/// A cross-attribute check that runs *after* all attributes on an item have been +/// finalized, so it can inspect the fully parsed attributes via +/// [`FinalizeContext::parsed_attrs`]. The [`Span`] is the span of the attribute the +/// check is associated with, used for diagnostics. +pub(crate) type FinalizeCheckFn = fn(&FinalizeContext<'_, '_>, Span); + +/// The result of finalizing a single attribute parser. +pub(crate) struct FinalizeOutput { + /// The attribute the parser produced, if any. + pub(crate) attr: Option, + /// A check to run once *all* attributes on the item have been finalized, together + /// with the span it should be reported at. Deferred so that it can inspect the fully + /// parsed attributes via [`FinalizeContext::parsed_attrs`]. + pub(crate) deferred_check: Option<(FinalizeCheckFn, Span)>, +} macro_rules! attribute_parsers { ( @@ -123,7 +140,11 @@ macro_rules! attribute_parsers { allowed_targets: <$names as crate::attributes::AttributeParser>::ALLOWED_TARGETS, finalizer: |cx| { let state = STATE_OBJECT.take(); - state.finalize(cx) + // Compute the deferred check (if any) before consuming + // the state in `finalize`. + let deferred_check = state.deferred_finalize_check(); + let attr = state.finalize(cx); + FinalizeOutput { attr, deferred_check } } }); } @@ -776,6 +797,15 @@ pub(crate) struct FinalizeContext<'p, 'sess> { /// Usually, you should use normal attribute parsing logic instead, /// especially when making a *denylist* of other attributes. pub(crate) all_attrs: &'p [RefPathParser<'p>], + + /// All attributes that have been parsed on this syntax node. + /// + /// Unlike [`all_attrs`](Self::all_attrs), which only contains the *paths* of the + /// attributes, this contains the fully parsed attributes. It is only populated when + /// running the deferred `finalize_check`s, which happen after all attributes on the + /// item have been finalized. During finalization itself this is empty, since the + /// attributes are not all available yet. + pub(crate) parsed_attrs: &'p [Attribute], } impl<'p, 'sess: 'p> Deref for FinalizeContext<'p, 'sess> { diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 54ba6c189b544..0fc38bfe65114 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -18,7 +18,8 @@ use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym}; use crate::attributes::AttributeSafety; use crate::context::{ - ATTRIBUTE_PARSERS, AcceptContext, FinalizeContext, FinalizeFn, SharedContext, + ATTRIBUTE_PARSERS, AcceptContext, FinalizeCheckFn, FinalizeContext, FinalizeFn, FinalizeOutput, + SharedContext, }; use crate::parser::{AllowExprMetavar, ArgParser, PathParser, RefPathParser}; use crate::session_diagnostics::ParsedDescription; @@ -448,8 +449,14 @@ impl<'sess> AttributeParser<'sess> { } synthetic_attr_state.finalize_synthetic_attrs(&mut attributes); + + // First, run all finalizers to produce the parsed attributes. Cross-attribute + // checks that need to inspect the fully parsed attributes are deferred until all + // finalizers have run (see below), since the parsed attributes are not yet all + // available here. + let mut deferred_checks: Vec<(FinalizeCheckFn, Span)> = Vec::new(); for f in &finalizers { - if let Some(attr) = f(&mut FinalizeContext { + let FinalizeOutput { attr, deferred_check } = f(&mut FinalizeContext { shared: SharedContext { cx: self, target_span, @@ -459,9 +466,34 @@ impl<'sess> AttributeParser<'sess> { has_lint_been_emitted: AtomicBool::new(false), }, all_attrs: &attr_paths, - }) { + parsed_attrs: &[], + }); + if let Some(attr) = attr { attributes.push(Attribute::Parsed(attr)); } + if let Some(deferred_check) = deferred_check { + deferred_checks.push(deferred_check); + } + } + + // Now that all attributes have been parsed, run the deferred checks. These can + // inspect the fully parsed attributes via `FinalizeContext::parsed_attrs`. + for (check, attr_span) in deferred_checks { + check( + &FinalizeContext { + shared: SharedContext { + cx: self, + target_span, + target, + emit_lint: &mut emit_lint, + #[cfg(debug_assertions)] + has_lint_been_emitted: AtomicBool::new(false), + }, + all_attrs: &attr_paths, + parsed_attrs: &attributes, + }, + attr_span, + ); } if !matches!(self.should_emit, ShouldEmit::Nothing) && target == Target::WherePredicate { From d2777f44151aa8b3d5d6955a483237518806dea2 Mon Sep 17 00:00:00 2001 From: Obei Sideg Date: Sun, 28 Jun 2026 14:51:49 +0300 Subject: [PATCH 29/31] Move `check_rustc_pub_transparent` into attribute parser the check that `#[rustc_pub_transparent]` is only applied to `#[repr(transparent)]` types needs to see the parsed `Repr` attribute, so it now lives in `RustcPubTransparentParser::finalize_check`, using the freshly available `FinalizeContext::parsed_attrs`. --- .../src/attributes/lint_helpers.rs | 14 ++++++++++++++ .../rustc_attr_parsing/src/session_diagnostics.rs | 9 +++++++++ compiler/rustc_passes/src/check_attr.rs | 12 +----------- compiler/rustc_passes/src/diagnostics.rs | 9 --------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs index 38afeea07794a..85665ad2bb2a8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs +++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs @@ -1,6 +1,9 @@ use rustc_feature::AttributeStability; +use rustc_hir::attrs::ReprAttr; +use rustc_hir::find_attr; use super::prelude::*; +use crate::session_diagnostics::RustcPubTransparent; pub(crate) struct RustcAsPtrParser; impl NoArgsAttributeParser for RustcAsPtrParser { @@ -26,6 +29,17 @@ impl NoArgsAttributeParser for RustcPubTransparentParser { ]); const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPubTransparent; + + fn finalize_check(cx: &FinalizeContext<'_, '_>, attr_span: Span) { + // `#[rustc_pub_transparent]` may only be applied to `#[repr(transparent)]` types. + let is_transparent = find_attr!( + cx.parsed_attrs, + Repr { reprs, .. } if reprs.iter().any(|(r, _)| r == &ReprAttr::ReprTransparent) + ); + if !is_transparent { + cx.emit_err(RustcPubTransparent { span: cx.target_span, attr_span }); + } + } } pub(crate) struct RustcPassByValueParser; diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index cbcec3ac22231..a5f6e7afc3dcc 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -20,6 +20,15 @@ pub(crate) struct BothFfiConstAndPure { pub attr_span: Span, } +#[derive(Diagnostic)] +#[diag("attribute should be applied to `#[repr(transparent)]` types")] +pub(crate) struct RustcPubTransparent { + #[primary_span] + pub attr_span: Span, + #[label("not a `#[repr(transparent)]` type")] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("{$attr_str} attribute cannot have empty value")] pub(crate) struct DocAliasEmpty<'a> { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3139c8746b95a..506f9a1e81327 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -207,9 +207,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcDumpObjectLifetimeDefaults => { self.check_dump_object_lifetime_defaults(hir_id); } - &AttributeKind::RustcPubTransparent(attr_span) => { - self.check_rustc_pub_transparent(attr_span, span, attrs) - } AttributeKind::Naked(..) => self.check_naked(hir_id, target), AttributeKind::TrackCaller(attr_span) => { self.check_track_caller(hir_id, *attr_span, attrs, target) @@ -386,6 +383,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) => (), AttributeKind::RustcPreserveUbChecks => (), AttributeKind::RustcProcMacroDecls => (), + AttributeKind::RustcPubTransparent(..) => (), AttributeKind::RustcReallocator => (), AttributeKind::RustcRegions => (), AttributeKind::RustcReservationImpl(..) => (), @@ -1592,14 +1590,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_rustc_pub_transparent(&self, attr_span: Span, span: Span, attrs: &[Attribute]) { - if !find_attr!(attrs, Repr { reprs, .. } => reprs.iter().any(|(r, _)| r == &ReprAttr::ReprTransparent)) - .unwrap_or(false) - { - self.dcx().emit_err(diagnostics::RustcPubTransparent { span, attr_span }); - } - } - fn check_rustc_force_inline(&self, hir_id: HirId, attrs: &[Attribute], target: Target) { if let (Target::Closure, None) = ( target, diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index 2589941d7dae1..664c4acc8ecc7 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -242,15 +242,6 @@ pub(crate) struct RustcAllowConstFnUnstable { pub span: Span, } -#[derive(Diagnostic)] -#[diag("attribute should be applied to `#[repr(transparent)]` types")] -pub(crate) struct RustcPubTransparent { - #[primary_span] - pub attr_span: Span, - #[label("not a `#[repr(transparent)]` type")] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("attribute cannot be applied to a `async`, `gen` or `async gen` function")] pub(crate) struct RustcForceInlineCoro { From 02076ac55a3006cfd42950f04d8f10b6930ee03a Mon Sep 17 00:00:00 2001 From: Obei Sideg Date: Fri, 10 Jul 2026 21:12:15 +0300 Subject: [PATCH 30/31] Add `FinalizeCheckContext` for attribute parser --- .../src/attributes/codegen_attrs.rs | 2 +- .../src/attributes/link_attrs.rs | 2 +- .../src/attributes/lint_helpers.rs | 2 +- .../rustc_attr_parsing/src/attributes/mod.rs | 35 +++++++------- .../src/attributes/prelude.rs | 2 +- compiler/rustc_attr_parsing/src/context.rs | 47 +++++++++++++++---- compiler/rustc_attr_parsing/src/interface.rs | 9 ++-- 7 files changed, 64 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 1de0bba71ffd2..15c4714624a63 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -541,7 +541,7 @@ impl CombineAttributeParser for TargetFeatureParser { parse_tf_attribute(cx, args) } - fn finalize_check(cx: &FinalizeContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { // `#[target_feature]` is incompatible with lang item functions, // except on WASM where calling target-feature functions is safe (see #84988). if !cx.sess().target.is_like_wasm && !cx.sess().opts.actually_rustdoc { diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index c76661b80e11d..07713590ff2e9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -576,7 +576,7 @@ impl NoArgsAttributeParser for FfiPureParser { const STABILITY: AttributeStability = unstable!(ffi_pure); const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; - fn finalize_check(cx: &FinalizeContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { // `#[ffi_const]` functions cannot be `#[ffi_pure]`. if cx.all_attrs.iter().any(|a| a.word_is(sym::ffi_const)) { cx.emit_err(BothFfiConstAndPure { attr_span }); diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs index 85665ad2bb2a8..8011beb05546f 100644 --- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs +++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs @@ -30,7 +30,7 @@ impl NoArgsAttributeParser for RustcPubTransparentParser { const STABILITY: AttributeStability = unstable!(rustc_attrs); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPubTransparent; - fn finalize_check(cx: &FinalizeContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { // `#[rustc_pub_transparent]` may only be applied to `#[repr(transparent)]` types. let is_transparent = find_attr!( cx.parsed_attrs, diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index f1122779eecc0..0adae406df5c6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -26,7 +26,7 @@ use rustc_span::edition::Edition; use rustc_span::{Span, Symbol}; use thin_vec::ThinVec; -use crate::context::{AcceptContext, FinalizeCheckFn, FinalizeContext}; +use crate::context::{AcceptContext, FinalizeCheckContext, FinalizeCheckFn, FinalizeContext}; use crate::parser::ArgParser; use crate::session_diagnostics::UnusedMultiple; use crate::target_checking::AllowedTargets; @@ -123,7 +123,7 @@ pub(crate) trait AttributeParser: Default + 'static { /// span it should be reported at. /// /// Running after finalization means the check can inspect the fully parsed attributes - /// via [`FinalizeContext::parsed_attrs`], which are not yet all available during + /// via [`FinalizeCheckContext::parsed_attrs`], which are not yet all available during /// [`finalize`](Self::finalize). This is queried right before `finalize` consumes the /// parser state. /// @@ -162,13 +162,14 @@ pub(crate) trait SingleAttributeParser: 'static { /// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`] fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option; - /// Optional cross-attribute validation, run once during finalization after all - /// attributes on the item have been parsed. Unlike [`convert`](Self::convert), this - /// has access to the sibling attributes via [`FinalizeContext::all_attrs`], so it can - /// reject incompatible combinations. `attr_span` is the span of this attribute. + /// Optional cross-attribute validation, run once *after* all attributes on the item + /// have been finalized. Unlike [`convert`](Self::convert), this has access to the + /// sibling attributes via [`FinalizeCheckContext::all_attrs`] and the fully parsed + /// attributes via [`FinalizeCheckContext::parsed_attrs`], so it can reject incompatible + /// combinations. `attr_span` is the span of this attribute. /// /// Defaults to a no-op. - fn finalize_check(_cx: &FinalizeContext<'_, '_>, _attr_span: Span) {} + fn finalize_check(_cx: &FinalizeCheckContext<'_, '_>, _attr_span: Span) {} } /// Use in combination with [`SingleAttributeParser`]. @@ -287,13 +288,14 @@ pub(crate) trait NoArgsAttributeParser: 'static { /// Create the [`AttributeKind`] given attribute's [`Span`]. const CREATE: fn(Span) -> AttributeKind; - /// Optional cross-attribute validation, run once during finalization after all - /// attributes on the item have been parsed. Has access to the sibling attributes via - /// [`FinalizeContext::all_attrs`], so it can reject incompatible combinations. + /// Optional cross-attribute validation, run once *after* all attributes on the item + /// have been finalized. Has access to the sibling attributes via + /// [`FinalizeCheckContext::all_attrs`] and the fully parsed attributes via + /// [`FinalizeCheckContext::parsed_attrs`], so it can reject incompatible combinations. /// `attr_span` is the span of this attribute. /// /// Defaults to a no-op. - fn finalize_check(_cx: &FinalizeContext<'_, '_>, _attr_span: Span) {} + fn finalize_check(_cx: &FinalizeCheckContext<'_, '_>, _attr_span: Span) {} } pub(crate) struct WithoutArgs(PhantomData); @@ -317,7 +319,7 @@ impl SingleAttributeParser for WithoutArgs { Some(T::CREATE(cx.attr_span)) } - fn finalize_check(cx: &FinalizeContext<'_, '_>, attr_span: Span) { + fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { T::finalize_check(cx, attr_span) } } @@ -354,13 +356,14 @@ pub(crate) trait CombineAttributeParser: 'static { args: &ArgParser, ) -> impl IntoIterator; - /// Optional cross-attribute validation, run once during finalization after all - /// attributes on the item have been parsed. Has access to the sibling attributes via - /// [`FinalizeContext::all_attrs`], so it can reject incompatible combinations. + /// Optional cross-attribute validation, run once *after* all attributes on the item + /// have been finalized. Has access to the sibling attributes via + /// [`FinalizeCheckContext::all_attrs`] and the fully parsed attributes via + /// [`FinalizeCheckContext::parsed_attrs`], so it can reject incompatible combinations. /// `attr_span` is the span of the first attribute that was encountered. /// /// Defaults to a no-op. - fn finalize_check(_cx: &FinalizeContext<'_, '_>, _attr_span: Span) {} + fn finalize_check(_cx: &FinalizeCheckContext<'_, '_>, _attr_span: Span) {} } /// Use in combination with [`CombineAttributeParser`]. diff --git a/compiler/rustc_attr_parsing/src/attributes/prelude.rs b/compiler/rustc_attr_parsing/src/attributes/prelude.rs index 4dd8f715f4387..744d5368580d8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/prelude.rs +++ b/compiler/rustc_attr_parsing/src/attributes/prelude.rs @@ -15,7 +15,7 @@ pub(super) use crate::attributes::{ }; // contexts #[doc(hidden)] -pub(super) use crate::context::{AcceptContext, FinalizeContext}; +pub(super) use crate::context::{AcceptContext, FinalizeCheckContext, FinalizeContext}; #[doc(hidden)] pub(super) use crate::parser::*; // target checking diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index dd8a77fb38c4f..929b34d4a36bc 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -99,9 +99,9 @@ pub(crate) type FinalizeFn = fn(&mut FinalizeContext<'_, '_>) -> FinalizeOutput; /// A cross-attribute check that runs *after* all attributes on an item have been /// finalized, so it can inspect the fully parsed attributes via -/// [`FinalizeContext::parsed_attrs`]. The [`Span`] is the span of the attribute the +/// [`FinalizeCheckContext::parsed_attrs`]. The [`Span`] is the span of the attribute the /// check is associated with, used for diagnostics. -pub(crate) type FinalizeCheckFn = fn(&FinalizeContext<'_, '_>, Span); +pub(crate) type FinalizeCheckFn = fn(&FinalizeCheckContext<'_, '_>, Span); /// The result of finalizing a single attribute parser. pub(crate) struct FinalizeOutput { @@ -109,7 +109,7 @@ pub(crate) struct FinalizeOutput { pub(crate) attr: Option, /// A check to run once *all* attributes on the item have been finalized, together /// with the span it should be reported at. Deferred so that it can inspect the fully - /// parsed attributes via [`FinalizeContext::parsed_attrs`]. + /// parsed attributes via [`FinalizeCheckContext::parsed_attrs`]. pub(crate) deferred_check: Option<(FinalizeCheckFn, Span)>, } @@ -797,18 +797,45 @@ pub(crate) struct FinalizeContext<'p, 'sess> { /// Usually, you should use normal attribute parsing logic instead, /// especially when making a *denylist* of other attributes. pub(crate) all_attrs: &'p [RefPathParser<'p>], +} + +impl<'p, 'sess: 'p> Deref for FinalizeContext<'p, 'sess> { + type Target = SharedContext<'p, 'sess>; + + fn deref(&self) -> &Self::Target { + &self.shared + } +} + +impl<'p, 'sess: 'p> DerefMut for FinalizeContext<'p, 'sess> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.shared + } +} + +/// Context given to deferred cross-attribute checks (`finalize_check`). +/// +/// These checks run *after* every attribute on an item has been finalized, so unlike +/// [`FinalizeContext`] this context can also inspect the fully parsed attributes via +/// [`parsed_attrs`](Self::parsed_attrs). +pub(crate) struct FinalizeCheckContext<'p, 'sess> { + pub(crate) shared: SharedContext<'p, 'sess>, + + /// A list of all attribute on this syntax node. + /// + /// Useful for compatibility checks with other attributes. + /// + /// Unlike [`parsed_attrs`](Self::parsed_attrs), this only contains the *paths* of the + /// attributes. + pub(crate) all_attrs: &'p [RefPathParser<'p>], /// All attributes that have been parsed on this syntax node. /// - /// Unlike [`all_attrs`](Self::all_attrs), which only contains the *paths* of the - /// attributes, this contains the fully parsed attributes. It is only populated when - /// running the deferred `finalize_check`s, which happen after all attributes on the - /// item have been finalized. During finalization itself this is empty, since the - /// attributes are not all available yet. + /// Unlike [`all_attrs`](Self::all_attrs), this contains the fully parsed attributes. pub(crate) parsed_attrs: &'p [Attribute], } -impl<'p, 'sess: 'p> Deref for FinalizeContext<'p, 'sess> { +impl<'p, 'sess: 'p> Deref for FinalizeCheckContext<'p, 'sess> { type Target = SharedContext<'p, 'sess>; fn deref(&self) -> &Self::Target { @@ -816,7 +843,7 @@ impl<'p, 'sess: 'p> Deref for FinalizeContext<'p, 'sess> { } } -impl<'p, 'sess: 'p> DerefMut for FinalizeContext<'p, 'sess> { +impl<'p, 'sess: 'p> DerefMut for FinalizeCheckContext<'p, 'sess> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.shared } diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 0fc38bfe65114..cb05b8de06ce4 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -18,8 +18,8 @@ use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym}; use crate::attributes::AttributeSafety; use crate::context::{ - ATTRIBUTE_PARSERS, AcceptContext, FinalizeCheckFn, FinalizeContext, FinalizeFn, FinalizeOutput, - SharedContext, + ATTRIBUTE_PARSERS, AcceptContext, FinalizeCheckContext, FinalizeCheckFn, FinalizeContext, + FinalizeFn, FinalizeOutput, SharedContext, }; use crate::parser::{AllowExprMetavar, ArgParser, PathParser, RefPathParser}; use crate::session_diagnostics::ParsedDescription; @@ -466,7 +466,6 @@ impl<'sess> AttributeParser<'sess> { has_lint_been_emitted: AtomicBool::new(false), }, all_attrs: &attr_paths, - parsed_attrs: &[], }); if let Some(attr) = attr { attributes.push(Attribute::Parsed(attr)); @@ -477,10 +476,10 @@ impl<'sess> AttributeParser<'sess> { } // Now that all attributes have been parsed, run the deferred checks. These can - // inspect the fully parsed attributes via `FinalizeContext::parsed_attrs`. + // inspect the fully parsed attributes via `FinalizeCheckContext::parsed_attrs`. for (check, attr_span) in deferred_checks { check( - &FinalizeContext { + &FinalizeCheckContext { shared: SharedContext { cx: self, target_span, From 8c6aba08af01d25fd4d6d8b08570c544c5240e29 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Mon, 20 Jul 2026 10:30:36 -0700 Subject: [PATCH 31/31] Set the rustc lib path for unstable-book-gen When configured with `rust.rpath = false` or using any other custom library configurations, running `rustc` requires a library path too. This was missing from `unstable-book-gen`'s new `rustc -Zhelp`. --- src/bootstrap/src/core/build_steps/doc.rs | 4 ++++ src/tools/unstable-book-gen/src/main.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 4787578e19db0..9299a47c70c57 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -1319,6 +1319,10 @@ impl Step for UnstableBookGen { cmd.arg(rustc_path); cmd.arg(out); + // Running rustc requires the library path if rust.rpath = false + // or any other libraries are in a custom location. + builder.add_rustc_lib_path(self.build_compiler, &mut cmd); + cmd.run(builder); } } diff --git a/src/tools/unstable-book-gen/src/main.rs b/src/tools/unstable-book-gen/src/main.rs index 923ca65b25501..e95d00f96c15e 100644 --- a/src/tools/unstable-book-gen/src/main.rs +++ b/src/tools/unstable-book-gen/src/main.rs @@ -128,6 +128,7 @@ fn collect_compiler_flags(rustc_path: impl AsRef) -> Features { rustc.env("RUSTC_BOOTSTRAP", "1"); rustc.arg("-Zhelp"); let output = t!(rustc.output()); + assert!(output.status.success(), "`rustc -Zhelp` failed: {output:?}"); let help_str = t!(String::from_utf8(output.stdout)); let parts = help_str.split("\n -Z").collect::>(); assert!(!parts[1..].is_empty(), "no -Z options were found");