From 5dd05009260d7678b8f7b0f3515a515013f9b03a Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Mon, 13 Apr 2026 17:37:16 +0000 Subject: [PATCH 01/30] Windows: Cache the pipe filesystem handle Also use the `\Device\NamedPipe\` directly instead of the symlink to it. --- .../std/src/sys/process/windows/child_pipe.rs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/library/std/src/sys/process/windows/child_pipe.rs b/library/std/src/sys/process/windows/child_pipe.rs index b848435ac275..311272fe05fa 100644 --- a/library/std/src/sys/process/windows/child_pipe.rs +++ b/library/std/src/sys/process/windows/child_pipe.rs @@ -1,6 +1,8 @@ use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; use crate::ops::Neg; use crate::os::windows::prelude::*; +use crate::sync::atomic::Atomic; +use crate::sync::atomic::Ordering::Relaxed; use crate::sys::handle::Handle; use crate::sys::{FromInner, IntoInner, api, c}; use crate::{mem, ptr}; @@ -70,10 +72,15 @@ pub(super) fn child_pipe(ours_readable: bool, their_handle_inheritable: bool) -> let mut object_attributes = c::OBJECT_ATTRIBUTES::default(); object_attributes.Length = size_of::() as u32; - // Open a handle to the pipe filesystem (`\??\PIPE\`). - // This will be used when creating a new annon pipe. - let pipe_fs = { - let path = api::unicode_str!(r"\??\PIPE\"); + // Open a handle to the pipe filesystem (`\Device\NamedPipe\`) and cache it. + // This will be used when creating a new anonymous pipe. + static PIPE_FS: Atomic = Atomic::::new(ptr::null_mut()); + let pipe_fs = if let handle = PIPE_FS.load(Relaxed) + && !handle.is_null() + { + handle + } else { + let path = api::unicode_str!(r"\Device\NamedPipe\"); object_attributes.ObjectName = path.as_ptr(); let mut pipe_fs = ptr::null_mut(); let status = c::NtOpenFile( @@ -85,7 +92,13 @@ pub(super) fn child_pipe(ours_readable: bool, their_handle_inheritable: bool) -> c::FILE_SYNCHRONOUS_IO_NONALERT, // synchronous access ); if c::nt_success(status) { - Handle::from_raw_handle(pipe_fs) + match PIPE_FS.compare_exchange(ptr::null_mut(), pipe_fs, Relaxed, Relaxed) { + Ok(_) => pipe_fs, + Err(existing) => { + c::CloseHandle(pipe_fs); + existing + } + } } else { return Err(io::Error::from_raw_os_error(c::RtlNtStatusToDosError(status) as i32)); } @@ -104,7 +117,7 @@ pub(super) fn child_pipe(ours_readable: bool, their_handle_inheritable: bool) -> let ours = { // Use the pipe filesystem as the root directory. // With no name provided, an anonymous pipe will be created. - object_attributes.RootDirectory = pipe_fs.as_raw_handle(); + object_attributes.RootDirectory = pipe_fs; // A negative timeout value is a relative time (rather than an absolute time). // The time is given in 100's of nanoseconds so this is 50 milliseconds. From bb9788bd42f459094ec6839f151530bba66c0149 Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Tue, 14 Apr 2026 19:11:41 +0300 Subject: [PATCH 02/30] Add _mm512_permutexvar_epi64 shim --- src/tools/miri/src/shims/x86/avx512.rs | 4 ++-- src/tools/miri/src/shims/x86/mod.rs | 23 ++++++++++++++---- .../pass/shims/x86/intrinsics-x86-avx512.rs | 24 +++++++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/src/shims/x86/avx512.rs b/src/tools/miri/src/shims/x86/avx512.rs index 8e1d22d723e7..fe4adf971c0d 100644 --- a/src/tools/miri/src/shims/x86/avx512.rs +++ b/src/tools/miri/src/shims/x86/avx512.rs @@ -104,8 +104,8 @@ fn emulate_x86_avx512_intrinsic( pmaddbw(this, left, right, dest)?; } - // Used to implement the _mm512_permutexvar_epi32 function. - "permvar.si.512" => { + // Used to implement the _mm512_permutexvar_epi32/_mm512_permutexvar_epi64 functions. + "permvar.si.512" | "permvar.di.512" => { let [left, right] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs index ce6538c8ca27..e6e7f4b6f09f 100644 --- a/src/tools/miri/src/shims/x86/mod.rs +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -1056,12 +1056,22 @@ fn pmaddbw<'tcx>( interp_ok(()) } -/// Shuffle 32-bit integers in `values` across lanes using the corresponding -/// index in `indices`, and store the results in dst. +/// Shuffle elements in `values` across lanes using the corresponding index in +/// `indices`, and store the results in `dest`. +/// +/// This helper is shared by both the 32-bit-lane and 64-bit-lane AVX +/// permute-by-index intrinsics. The element type is taken from `values` and +/// `dest`, while the index lanes are interpreted at their full width (`i32` or +/// `i64`, depending on the intrinsic). +/// +/// For a vector with `N` lanes, only the low `log2(N)` bits of each index are +/// used. Equivalently, lane `i` of the result is copied from +/// `values[indices[i] & (N - 1)]`. /// /// /// /// +/// fn permute<'tcx>( ecx: &mut crate::MiriInterpCx<'tcx>, values: &OpTy<'tcx>, @@ -1075,18 +1085,21 @@ fn permute<'tcx>( // fn permd(a: u32x8, b: u32x8) -> u32x8; // fn permps(a: __m256, b: i32x8) -> __m256; // fn vpermd(a: i32x16, idx: i32x16) -> i32x16; + // fn vpermq(a: i64x8, b: i64x8) -> i64x8; assert_eq!(dest_len, values_len); assert_eq!(dest_len, indices_len); // Only use the lower 3 bits to index into a vector with 8 lanes, // or the lower 4 bits when indexing into a 16-lane vector. assert!(dest_len.is_power_of_two()); - let mask = u32::try_from(dest_len).unwrap().strict_sub(1); + let mask = u128::from(dest_len).strict_sub(1); for i in 0..dest_len { let dest = ecx.project_index(&dest, i)?; - let index = ecx.read_scalar(&ecx.project_index(&indices, i)?)?.to_u32()?; - let element = ecx.project_index(&values, (index & mask).into())?; + let index_place = ecx.project_index(&indices, i)?; + let index = ecx.read_scalar(&index_place)?.to_uint(index_place.layout.size)?; + // `mask` is at most `dest_len - 1` which fits in a `u64`, so this cannot fail. + let element = ecx.project_index(&values, u64::try_from(index & mask).unwrap())?; ecx.copy_op(&element, &dest)?; } diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs index e1e23eda8428..0417a4cbc679 100644 --- a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs +++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-avx512.rs @@ -219,6 +219,30 @@ unsafe fn test_mm512_permutexvar_epi32() { } test_mm512_permutexvar_epi32(); + #[target_feature(enable = "avx512f")] + unsafe fn test_mm512_permutexvar_epi64() { + let a = _mm512_setr_epi64(100, 200, 300, 400, 500, 600, 700, 800); + + // Mirrors stdarch's basic sanity check. + let idx = _mm512_set1_epi64(1); + let r = _mm512_permutexvar_epi64(idx, a); + let e = _mm512_set1_epi64(200); + assert_eq_m512i(r, e); + + // This must permute across the full 512-bit register, not within 128-bit lanes. + let idx = _mm512_setr_epi64(7, 0, 5, 2, 6, 1, 4, 3); + let r = _mm512_permutexvar_epi64(idx, a); + let e = _mm512_setr_epi64(800, 100, 600, 300, 700, 200, 500, 400); + assert_eq_m512i(r, e); + + // Only the low 3 bits of each 64-bit index are used. + let idx = _mm512_setr_epi64(8, 15, -1, i64::MIN, 0, 1, 2, 3); + let r = _mm512_permutexvar_epi64(idx, a); + let e = _mm512_setr_epi64(100, 800, 800, 100, 100, 200, 300, 400); + assert_eq_m512i(r, e); + } + test_mm512_permutexvar_epi64(); + #[target_feature(enable = "avx512bw")] unsafe fn test_mm512_shuffle_epi8() { #[rustfmt::skip] From c5673329278dd3f5e3c7e838b9595cfbcd02fda5 Mon Sep 17 00:00:00 2001 From: Mahdi Ali-Raihan Date: Sat, 11 Apr 2026 16:15:29 -0400 Subject: [PATCH 03/30] Implemented PermissionsExt ACP on Windows, which provides functions/utilities to observe, set, and create a Permissions struct with certain file attributes --- library/std/src/os/windows/fs.rs | 66 ++++++++++++++++++++++++++++++- library/std/src/sys/fs/windows.rs | 10 +++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 7fd46b31f7d8..54d5cafe15ec 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -4,11 +4,11 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::fs::{self, Metadata, OpenOptions}; +use crate::fs::{self, Metadata, OpenOptions, Permissions}; use crate::io::BorrowedCursor; use crate::path::Path; use crate::sealed::Sealed; -use crate::sys::{AsInner, AsInnerMut, IntoInner}; +use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::time::SystemTime; use crate::{io, sys}; @@ -368,6 +368,68 @@ fn freeze_last_write_time(&mut self, freeze: bool) -> &mut Self { } } +/// Windows-specific extensions to [`fs::Permissions`]. This extension trait +/// provides extra utilities to shows what Windows file attributes are enabled +/// in [`Permissions`] and to manually set file attributes on [`Permissions`]. +/// +/// See Microsoft's [`File Attribute Constants`] page to know what file +/// attribute metadata are defined and stored on Windows files. +/// +/// [`Permissions`]: fs::Permissions +/// [`File Attribute Constants`]: +/// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants +/// +/// # Example +/// +/// ```no_run +/// #![feature(windows_permissions_ext)] +/// use std::fs::Permissions; +/// use std::os::windows::fs::PermissionsExt; +/// +/// const FILE_ATTRIBUTE_SYSTEM: u32 = 0x4; +/// const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20; +/// let my_file_attr = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE; +/// let mut permissions = Permissions::from_file_attributes(my_file_attr); +/// assert_eq!(permissions.file_attributes(), my_file_attr); +/// +/// const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2; +/// let new_file_attr = permissions.file_attributes() | FILE_ATTRIBUTE_HIDDEN; +/// permissions.set_file_attributes(new_file_attr); +/// assert_eq!(permissions.file_attributes(), new_file_attr); +/// ``` +#[unstable(feature = "windows_permissions_ext", issue = "152956")] +pub trait PermissionsExt: Sealed { + /// Returns the file attribute bits. + #[unstable(feature = "windows_permissions_ext", issue = "152956")] + fn file_attributes(&self) -> u32; + + /// Sets the file attribute bits. + #[unstable(feature = "windows_permissions_ext", issue = "152956")] + fn set_file_attributes(&mut self, mask: u32); + + /// Creates a new instance from the given file attribute bits. + #[unstable(feature = "windows_permissions_ext", issue = "152956")] + fn from_file_attributes(mask: u32) -> Self; +} + +#[unstable(feature = "windows_permissions_ext", issue = "152956")] +impl Sealed for fs::Permissions {} + +#[unstable(feature = "windows_permissions_ext", issue = "152956")] +impl PermissionsExt for fs::Permissions { + fn file_attributes(&self) -> u32 { + self.as_inner().file_attributes() + } + + fn set_file_attributes(&mut self, mask: u32) { + *self = Permissions::from_inner(FromInner::from_inner(mask)); + } + + fn from_file_attributes(mask: u32) -> Self { + Permissions::from_inner(FromInner::from_inner(mask)) + } +} + /// Windows-specific extensions to [`fs::Metadata`]. /// /// The data members that this trait exposes correspond to the members diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 74854cdeb498..e0b02670264d 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1167,6 +1167,16 @@ pub fn set_readonly(&mut self, readonly: bool) { self.attrs &= !c::FILE_ATTRIBUTE_READONLY; } } + + pub fn file_attributes(&self) -> u32 { + self.attrs as u32 + } +} + +impl FromInner for FilePermissions { + fn from_inner(attrs: u32) -> FilePermissions { + FilePermissions { attrs } + } } impl FileTimes { From e1ef601a730e172dfa5a390500fed499cd94ed9a Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 21 Apr 2026 09:03:22 +1000 Subject: [PATCH 04/30] Adjust Documentation for `RawOsError` The hyperlink to `std::io::Error` will not be valid when moved to `core::io`. There is also a typo which I might as well fix while I'm here. --- library/std/src/io/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/std/src/io/error.rs b/library/std/src/io/error.rs index 6f565bb37c53..35e2c8a29304 100644 --- a/library/std/src/io/error.rs +++ b/library/std/src/io/error.rs @@ -140,11 +140,11 @@ enum ErrorData { Custom(C), } -/// The type of raw OS error codes returned by [`Error::raw_os_error`]. +/// The type of raw OS error codes. /// /// This is an [`i32`] on all currently supported platforms, but platforms /// added in the future (such as UEFI) may use a different primitive type like -/// [`usize`]. Use `as`or [`into`] conversions where applicable to ensure maximum +/// [`usize`]. Use `as` or [`into`] conversions where applicable to ensure maximum /// portability. /// /// [`into`]: Into::into From 4cb72b3ca534f6119b40e6f7545e38f1dfde0f4a Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 21 Apr 2026 09:01:40 +1000 Subject: [PATCH 05/30] Adjust Usage of `RawOsError` Inconsistently referenced through `std::sys` and `std::io`. Choosing `std::io` as the canonical source to make migration to `core::io` cleaner. --- library/std/src/sys/io/error/motor.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/std/src/sys/io/error/motor.rs b/library/std/src/sys/io/error/motor.rs index 7d612d817cdd..3c22d5fcb7b0 100644 --- a/library/std/src/sys/io/error/motor.rs +++ b/library/std/src/sys/io/error/motor.rs @@ -1,7 +1,6 @@ use crate::io; -use crate::sys::io::RawOsError; -pub fn errno() -> RawOsError { +pub fn errno() -> io::RawOsError { // Not used in Motor OS because it is ambiguous: Motor OS // is micro-kernel-based, and I/O happens via a shared-memory // ring buffer, so an I/O operation that on a unix is a syscall @@ -57,7 +56,7 @@ pub fn decode_error_kind(code: io::RawOsError) -> io::ErrorKind { } } -pub fn error_string(errno: RawOsError) -> String { +pub fn error_string(errno: io::RawOsError) -> String { let error: moto_rt::Error = match errno { x if x < 0 => moto_rt::Error::Unknown, x if x > u16::MAX.into() => moto_rt::Error::Unknown, From 7653e5ea01c18a8245df9dd48ea706c112f69b28 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Tue, 21 Apr 2026 09:04:58 +1000 Subject: [PATCH 06/30] Move `RawOsError` to `core::io` --- library/core/src/io/error.rs | 14 ++++++++++++++ library/core/src/io/mod.rs | 2 ++ library/std/src/io/error.rs | 13 ++----------- library/std/src/lib.rs | 1 + library/std/src/sys/io/error/mod.rs | 5 ----- library/std/src/sys/io/mod.rs | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/library/core/src/io/error.rs b/library/core/src/io/error.rs index fe12de2952f0..ff50b2822d05 100644 --- a/library/core/src/io/error.rs +++ b/library/core/src/io/error.rs @@ -2,6 +2,20 @@ use crate::fmt; +/// The type of raw OS error codes. +/// +/// This is an [`i32`] on all currently supported platforms, but platforms +/// added in the future (such as UEFI) may use a different primitive type like +/// [`usize`]. Use `as` or [`into`] conversions where applicable to ensure maximum +/// portability. +/// +/// [`into`]: Into::into +#[unstable(feature = "raw_os_error_ty", issue = "107792")] +pub type RawOsError = cfg_select! { + target_os = "uefi" => usize, + _ => i32, +}; + /// A list specifying general categories of I/O error. /// /// This list is intended to grow over time and it is not recommended to diff --git a/library/core/src/io/mod.rs b/library/core/src/io/mod.rs index c34421523b64..2d8273dd1b2d 100644 --- a/library/core/src/io/mod.rs +++ b/library/core/src/io/mod.rs @@ -7,3 +7,5 @@ pub use self::borrowed_buf::{BorrowedBuf, BorrowedCursor}; #[unstable(feature = "core_io", issue = "154046")] pub use self::error::ErrorKind; +#[unstable(feature = "raw_os_error_ty", issue = "107792")] +pub use self::error::RawOsError; diff --git a/library/std/src/io/error.rs b/library/std/src/io/error.rs index 35e2c8a29304..360ca83c65a9 100644 --- a/library/std/src/io/error.rs +++ b/library/std/src/io/error.rs @@ -3,6 +3,8 @@ #[stable(feature = "rust1", since = "1.0.0")] pub use core::io::ErrorKind; +#[unstable(feature = "raw_os_error_ty", issue = "107792")] +pub use core::io::RawOsError; // On 64-bit platforms, `io::Error` may use a bit-packed representation to // reduce size. However, this representation assumes that error codes are @@ -140,17 +142,6 @@ enum ErrorData { Custom(C), } -/// The type of raw OS error codes. -/// -/// This is an [`i32`] on all currently supported platforms, but platforms -/// added in the future (such as UEFI) may use a different primitive type like -/// [`usize`]. Use `as` or [`into`] conversions where applicable to ensure maximum -/// portability. -/// -/// [`into`]: Into::into -#[unstable(feature = "raw_os_error_ty", issue = "107792")] -pub type RawOsError = sys::io::RawOsError; - // `#[repr(align(4))]` is probably redundant, it should have that value or // higher already. We include it just because repr_bitpacked.rs's encoding // requires an alignment >= 4 (note that `#[repr(align)]` will not reduce the diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 807befec1ad1..7287613bde46 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -363,6 +363,7 @@ #![feature(ptr_as_uninit)] #![feature(ptr_mask)] #![feature(random)] +#![feature(raw_os_error_ty)] #![feature(slice_internals)] #![feature(slice_ptr_get)] #![feature(slice_range)] diff --git a/library/std/src/sys/io/error/mod.rs b/library/std/src/sys/io/error/mod.rs index d7a0b9b4b301..4fca658a7dca 100644 --- a/library/std/src/sys/io/error/mod.rs +++ b/library/std/src/sys/io/error/mod.rs @@ -48,8 +48,3 @@ pub use generic::*; } } - -pub type RawOsError = cfg_select! { - target_os = "uefi" => usize, - _ => i32, -}; diff --git a/library/std/src/sys/io/mod.rs b/library/std/src/sys/io/mod.rs index b3587ab63696..445bcdef0aa1 100644 --- a/library/std/src/sys/io/mod.rs +++ b/library/std/src/sys/io/mod.rs @@ -62,7 +62,7 @@ mod is_terminal { target_os = "wasi", ))] pub use error::set_errno; -pub use error::{RawOsError, decode_error_kind, errno, error_string, is_interrupted}; +pub use error::{decode_error_kind, errno, error_string, is_interrupted}; pub use io_slice::{IoSlice, IoSliceMut}; pub use is_terminal::is_terminal; pub use kernel_copy::{CopyState, kernel_copy}; From a6ec2947a3b94bf9b7bb9e9ee2ad0d82f580e069 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Tue, 21 Apr 2026 04:15:26 +0000 Subject: [PATCH 07/30] Explicitly note that we're leaking a handle --- library/std/src/sys/process/windows/child_pipe.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/std/src/sys/process/windows/child_pipe.rs b/library/std/src/sys/process/windows/child_pipe.rs index 311272fe05fa..8d71e1c61f82 100644 --- a/library/std/src/sys/process/windows/child_pipe.rs +++ b/library/std/src/sys/process/windows/child_pipe.rs @@ -72,8 +72,12 @@ pub(super) fn child_pipe(ours_readable: bool, their_handle_inheritable: bool) -> let mut object_attributes = c::OBJECT_ATTRIBUTES::default(); object_attributes.Length = size_of::() as u32; - // Open a handle to the pipe filesystem (`\Device\NamedPipe\`) and cache it. + // Open a handle to the pipe filesystem (`\Device\NamedPipe\`). // This will be used when creating a new anonymous pipe. + // + // We cache the handle once so we can reuse it without needing to reopen it each time. + // NOTE: this means the handle may appear to be leaked but that's fine because + // it's only one handle and the OS will clean it up when the process exits. static PIPE_FS: Atomic = Atomic::::new(ptr::null_mut()); let pipe_fs = if let handle = PIPE_FS.load(Relaxed) && !handle.is_null() From 2f73eeea8b0f6a848c3a0cdba977a0783c57e4b9 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Apr 2026 17:46:49 +0200 Subject: [PATCH 08/30] unnamed_socket: do not introduce artifical short reads/writes --- src/tools/miri/src/shims/unix/unnamed_socket.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/unnamed_socket.rs index 74d6abf7b63d..2bec5640ab1b 100644 --- a/src/tools/miri/src/shims/unix/unnamed_socket.rs +++ b/src/tools/miri/src/shims/unix/unnamed_socket.rs @@ -126,11 +126,13 @@ fn write<'tcx>( } fn short_fd_operations(&self) -> bool { - // Pipes guarantee that sufficiently small accesses are not broken apart: - // . - // For now, we don't bother checking for the size, and just entirely disable - // short accesses on pipes. - matches!(self.fd_type, AnonSocketType::Socketpair) + // Linux de-facto guarantees (or at least, applications like tokio assume [1, 2]) that + // when a read/write on a streaming socket comes back short, the kernel buffer is + // empty/full. SO we can't do short reads/writes here. + // + // [1]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182 + // [2]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240 + false } fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription { From 17aae34a1fcc2b6de13eb4735ae293b4627a402c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Apr 2026 17:49:12 +0200 Subject: [PATCH 09/30] rename unnamed_socket -> virtual_socket --- src/tools/miri/src/concurrency/thread.rs | 4 +- src/tools/miri/src/shims/unix/mod.rs | 4 +- .../{unnamed_socket.rs => virtual_socket.rs} | 84 +++++++++---------- 3 files changed, 46 insertions(+), 46 deletions(-) rename src/tools/miri/src/shims/unix/{unnamed_socket.rs => virtual_socket.rs} (90%) diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index e9458cc3f456..f72bfe031d36 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -111,8 +111,8 @@ pub enum BlockReason { Epoll, /// Blocked on eventfd. Eventfd, - /// Blocked on unnamed_socket. - UnnamedSocket, + /// Blocked on virtual socket. + VirtualSocket, /// Blocked on an IO operation. IO, /// Blocked for any reason related to GenMC, such as `assume` statements (GenMC mode only). diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 5ea49926fb9f..c55a28bfa7b2 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -7,7 +7,7 @@ mod socket; mod sync; mod thread; -mod unnamed_socket; +mod virtual_socket; mod android; mod freebsd; @@ -25,7 +25,7 @@ pub use self::socket::EvalContextExt as _; pub use self::sync::EvalContextExt as _; pub use self::thread::{EvalContextExt as _, ThreadNameResult}; -pub use self::unnamed_socket::EvalContextExt as _; +pub use self::virtual_socket::EvalContextExt as _; // Make up some constants. const UID: u32 = 1000; diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs similarity index 90% rename from src/tools/miri/src/shims/unix/unnamed_socket.rs rename to src/tools/miri/src/shims/unix/virtual_socket.rs index 2bec5640ab1b..51092b80c1c6 100644 --- a/src/tools/miri/src/shims/unix/unnamed_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -1,6 +1,6 @@ -//! This implements "anonymous" sockets, that do not correspond to anything on the host system and +//! This implements "virtual" sockets, that do not correspond to anything on the host system and //! are entirely implemented inside Miri. -//! We also use the same infrastructure to implement unnamed pipes. +//! This is used to implement `socketpair` and `pipe`. use std::cell::{Cell, OnceCell, RefCell}; use std::collections::VecDeque; @@ -22,7 +22,7 @@ const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 0x34000; #[derive(Debug, PartialEq)] -enum AnonSocketType { +enum VirtualSocketType { // Either end of the socketpair fd. Socketpair, // Read end of the pipe. @@ -31,16 +31,16 @@ enum AnonSocketType { PipeWrite, } -/// One end of a pair of connected unnamed sockets. +/// One end of a pair of connected virtual sockets. #[derive(Debug)] -struct AnonSocket { +struct VirtualSocket { /// The buffer we are reading from, or `None` if this is the writing end of a pipe. /// (In that case, the peer FD will be the reading end of that pipe.) readbuf: Option>, - /// The `AnonSocket` file descriptor that is our "peer", and that holds the buffer we are + /// The `VirtualSocket` file descriptor that is our "peer", and that holds the buffer we are /// writing to. This is a weak reference because the other side may be closed before us; all /// future writes will then trigger EPIPE. - peer_fd: OnceCell>, + peer_fd: OnceCell>, /// Indicates whether the peer has lost data when the file description is closed. /// This flag is set to `true` if the peer's `readbuf` is non-empty at the time /// of closure. @@ -53,8 +53,8 @@ struct AnonSocket { blocked_write_tid: RefCell>, /// Whether this fd is non-blocking or not. is_nonblock: Cell, - // Differentiate between different AnonSocket fd types. - fd_type: AnonSocketType, + // Differentiate between different virtual socket fd types. + fd_type: VirtualSocketType, } #[derive(Debug)] @@ -69,17 +69,17 @@ fn new() -> Self { } } -impl AnonSocket { - fn peer_fd(&self) -> &WeakFileDescriptionRef { +impl VirtualSocket { + fn peer_fd(&self) -> &WeakFileDescriptionRef { self.peer_fd.get().unwrap() } } -impl FileDescription for AnonSocket { +impl FileDescription for VirtualSocket { fn name(&self) -> &'static str { match self.fd_type { - AnonSocketType::Socketpair => "socketpair", - AnonSocketType::PipeRead | AnonSocketType::PipeWrite => "pipe", + VirtualSocketType::Socketpair => "socketpair", + VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "pipe", } } @@ -111,7 +111,7 @@ fn read<'tcx>( ecx: &mut MiriInterpCx<'tcx>, finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - anonsocket_read(self, ptr, len, ecx, finish) + virtual_socket_read(self, ptr, len, ecx, finish) } fn write<'tcx>( @@ -122,7 +122,7 @@ fn write<'tcx>( ecx: &mut MiriInterpCx<'tcx>, finish: DynMachineCallback<'tcx, Result>, ) -> InterpResult<'tcx> { - anonsocket_write(self, ptr, len, ecx, finish) + virtual_socket_write(self, ptr, len, ecx, finish) } fn short_fd_operations(&self) -> bool { @@ -147,13 +147,13 @@ fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Sc // fd is closed, so we need to look at the original type of this socket, not at whether // the peer socket still exists. match self.fd_type { - AnonSocketType::Socketpair => { + VirtualSocketType::Socketpair => { flags |= ecx.eval_libc_i32("O_RDWR"); } - AnonSocketType::PipeRead => { + VirtualSocketType::PipeRead => { flags |= ecx.eval_libc_i32("O_RDONLY"); } - AnonSocketType::PipeWrite => { + VirtualSocketType::PipeWrite => { flags |= ecx.eval_libc_i32("O_WRONLY"); } } @@ -192,9 +192,9 @@ fn set_flags<'tcx>( } } -/// Write to AnonSocket based on the space available and return the written byte size. -fn anonsocket_write<'tcx>( - self_ref: FileDescriptionRef, +/// Write to VirtualSocket based on the space available and return the written byte size. +fn virtual_socket_write<'tcx>( + self_ref: FileDescriptionRef, ptr: Pointer, len: usize, ecx: &mut MiriInterpCx<'tcx>, @@ -230,11 +230,11 @@ fn anonsocket_write<'tcx>( // Block the current thread; only keep a weak ref for this. let weak_self_ref = FileDescriptionRef::downgrade(&self_ref); ecx.block_thread( - BlockReason::UnnamedSocket, + BlockReason::VirtualSocket, None, callback!( @capture<'tcx> { - weak_self_ref: WeakFileDescriptionRef, + weak_self_ref: WeakFileDescriptionRef, ptr: Pointer, len: usize, finish: DynMachineCallback<'tcx, Result>, @@ -244,7 +244,7 @@ fn anonsocket_write<'tcx>( // If we got unblocked, then our peer successfully upgraded its weak // ref to us. That means we can also upgrade our weak ref. let self_ref = weak_self_ref.upgrade().unwrap(); - anonsocket_write(self_ref, ptr, len, this, finish) + virtual_socket_write(self_ref, ptr, len, this, finish) } ), ); @@ -268,7 +268,7 @@ fn anonsocket_write<'tcx>( let waiting_threads = std::mem::take(&mut *peer_fd.blocked_read_tid.borrow_mut()); // FIXME: We can randomize the order of unblocking. for thread_id in waiting_threads { - ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?; + ecx.unblock_thread(thread_id, BlockReason::VirtualSocket)?; } // Notify epoll waiters: we might be no longer writable, peer might now be readable. // The notification to the peer seems to be always sent on Linux, even if the @@ -281,9 +281,9 @@ fn anonsocket_write<'tcx>( interp_ok(()) } -/// Read from AnonSocket and return the number of bytes read. -fn anonsocket_read<'tcx>( - self_ref: FileDescriptionRef, +/// Read from VirtualSocket and return the number of bytes read. +fn virtual_socket_read<'tcx>( + self_ref: FileDescriptionRef, ptr: Pointer, len: usize, ecx: &mut MiriInterpCx<'tcx>, @@ -318,11 +318,11 @@ fn anonsocket_read<'tcx>( // Block the current thread; only keep a weak ref for this. let weak_self_ref = FileDescriptionRef::downgrade(&self_ref); ecx.block_thread( - BlockReason::UnnamedSocket, + BlockReason::VirtualSocket, None, callback!( @capture<'tcx> { - weak_self_ref: WeakFileDescriptionRef, + weak_self_ref: WeakFileDescriptionRef, ptr: Pointer, len: usize, finish: DynMachineCallback<'tcx, Result>, @@ -332,7 +332,7 @@ fn anonsocket_read<'tcx>( // If we got unblocked, then our peer successfully upgraded its weak // ref to us. That means we can also upgrade our weak ref. let self_ref = weak_self_ref.upgrade().unwrap(); - anonsocket_read(self_ref, ptr, len, this, finish) + virtual_socket_read(self_ref, ptr, len, this, finish) } ), ); @@ -365,7 +365,7 @@ fn anonsocket_read<'tcx>( let waiting_threads = std::mem::take(&mut *peer_fd.blocked_write_tid.borrow_mut()); // FIXME: We can randomize the order of unblocking. for thread_id in waiting_threads { - ecx.unblock_thread(thread_id, BlockReason::UnnamedSocket)?; + ecx.unblock_thread(thread_id, BlockReason::VirtualSocket)?; } // Notify epoll waiters: peer is now writable. // Linux seems to always notify the peer if the read buffer is now empty. @@ -381,7 +381,7 @@ fn anonsocket_read<'tcx>( interp_ok(()) } -impl UnixFileDescription for AnonSocket { +impl UnixFileDescription for VirtualSocket { fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollEvents> { // We only check the status of EPOLLIN, EPOLLOUT, EPOLLHUP and EPOLLRDHUP flags. // If other event flags need to be supported in the future, the check should be added here. @@ -489,23 +489,23 @@ fn socketpair( // Generate file descriptions. let fds = &mut this.machine.fds; - let fd0 = fds.new_ref(AnonSocket { + let fd0 = fds.new_ref(VirtualSocket { readbuf: Some(RefCell::new(Buffer::new())), peer_fd: OnceCell::new(), peer_lost_data: Cell::new(false), blocked_read_tid: RefCell::new(Vec::new()), blocked_write_tid: RefCell::new(Vec::new()), is_nonblock: Cell::new(is_sock_nonblock), - fd_type: AnonSocketType::Socketpair, + fd_type: VirtualSocketType::Socketpair, }); - let fd1 = fds.new_ref(AnonSocket { + let fd1 = fds.new_ref(VirtualSocket { readbuf: Some(RefCell::new(Buffer::new())), peer_fd: OnceCell::new(), peer_lost_data: Cell::new(false), blocked_read_tid: RefCell::new(Vec::new()), blocked_write_tid: RefCell::new(Vec::new()), is_nonblock: Cell::new(is_sock_nonblock), - fd_type: AnonSocketType::Socketpair, + fd_type: VirtualSocketType::Socketpair, }); // Make the file descriptions point to each other. @@ -559,23 +559,23 @@ fn pipe2( // Generate file descriptions. // pipefd[0] refers to the read end of the pipe. let fds = &mut this.machine.fds; - let fd0 = fds.new_ref(AnonSocket { + let fd0 = fds.new_ref(VirtualSocket { readbuf: Some(RefCell::new(Buffer::new())), peer_fd: OnceCell::new(), peer_lost_data: Cell::new(false), blocked_read_tid: RefCell::new(Vec::new()), blocked_write_tid: RefCell::new(Vec::new()), is_nonblock: Cell::new(is_nonblock), - fd_type: AnonSocketType::PipeRead, + fd_type: VirtualSocketType::PipeRead, }); - let fd1 = fds.new_ref(AnonSocket { + let fd1 = fds.new_ref(VirtualSocket { readbuf: None, peer_fd: OnceCell::new(), peer_lost_data: Cell::new(false), blocked_read_tid: RefCell::new(Vec::new()), blocked_write_tid: RefCell::new(Vec::new()), is_nonblock: Cell::new(is_nonblock), - fd_type: AnonSocketType::PipeWrite, + fd_type: VirtualSocketType::PipeWrite, }); // Make the file descriptions point to each other. From bb80f4e4ba883e2fec2d442f2ee2cc0ed984f8d6 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:04:02 +0200 Subject: [PATCH 10/30] Use windows-sys 0.61 in tests This version no longer requires using big import libraries and instead uses raw-dylib. --- src/tools/miri/tests/deps/Cargo.lock | 88 +++------------------------- src/tools/miri/tests/deps/Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 82 deletions(-) diff --git a/src/tools/miri/tests/deps/Cargo.lock b/src/tools/miri/tests/deps/Cargo.lock index 2a1468d55ad0..1c31cc3f6120 100644 --- a/src/tools/miri/tests/deps/Cargo.lock +++ b/src/tools/miri/tests/deps/Cargo.lock @@ -45,7 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -281,7 +281,7 @@ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -298,7 +298,7 @@ dependencies = [ "page_size", "tempfile", "tokio", - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -383,7 +383,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -463,7 +463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -487,7 +487,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -503,7 +503,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -666,15 +666,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -684,71 +675,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/src/tools/miri/tests/deps/Cargo.toml b/src/tools/miri/tests/deps/Cargo.toml index fd301fc5cf32..73b3025212ec 100644 --- a/src/tools/miri/tests/deps/Cargo.toml +++ b/src/tools/miri/tests/deps/Cargo.toml @@ -26,7 +26,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "net", futures = { version = "0.3.0", default-features = false, features = ["alloc", "async-await"] } [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.60", features = [ +windows-sys = { version = "0.61", features = [ "Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem", From c77b198e4bef465ad220b794ca4686ff988aceec Mon Sep 17 00:00:00 2001 From: WhySoBad <49595640+WhySoBad@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:37:08 +0200 Subject: [PATCH 11/30] chore: disable short reads/writes for TCP sockets --- src/tools/miri/src/shims/unix/socket.rs | 41 +++++------------ .../miri/tests/pass-dep/libc/libc-socket.rs | 46 ------------------- 2 files changed, 11 insertions(+), 76 deletions(-) diff --git a/src/tools/miri/src/shims/unix/socket.rs b/src/tools/miri/src/shims/unix/socket.rs index 9d7d5a32f127..c553cd1f70e8 100644 --- a/src/tools/miri/src/shims/unix/socket.rs +++ b/src/tools/miri/src/shims/unix/socket.rs @@ -7,7 +7,6 @@ use mio::Interest; use mio::event::Source; use mio::net::{TcpListener, TcpStream}; -use rand::Rng; use rustc_abi::Size; use rustc_const_eval::interpret::{InterpResult, interp_ok}; use rustc_middle::throw_unsup_format; @@ -168,8 +167,13 @@ fn write<'tcx>( } fn short_fd_operations(&self) -> bool { - // Short accesses on TCP sockets are realistic and expected to happen. - true + // Linux de-facto guarantees (or at least, applications like tokio assume [1, 2]) that + // when a read/write on a streaming socket comes back short, the kernel buffer is + // empty/full. SO we can't do short reads/writes here. + // + // [1]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182 + // [2]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240 + false } fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription { @@ -652,18 +656,6 @@ fn send( return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); }; - // Non-deterministically decide to further reduce the length, simulating a partial send. - // We avoid reducing the write size to 0: the docs seem to be entirely fine with that, - // but the standard library is not (https://github.com/rust-lang/rust/issues/145959). - let length = if this.machine.short_fd_operations - && length >= 2 - && this.machine.rng.get_mut().random() - { - length / 2 - } else { - length - }; - let mut is_op_non_block = false; // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so @@ -774,21 +766,6 @@ fn recv( return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest); }; - // Non-deterministically decide to further reduce the length, simulating a partial receive. - // We don't simulate partial receives for lengths < 2 because the man page states that a - // return value of zero can only be returned in some special cases: - // "When a stream socket peer has performed an orderly shutdown, the return value will be 0 - // (the traditional "end-of-file" return). [...] The value 0 may also be returned if the - // requested number of bytes to receive from a stream socket was 0." - let length = if this.machine.short_fd_operations - && length >= 2 - && this.machine.rng.get_mut().random() - { - length / 2 // since `length` is at least 2, the result is still at least 1 - } else { - length - }; - let mut should_peek = false; let mut is_op_non_block = false; @@ -1502,6 +1479,8 @@ fn try_non_block_send( // This is a *non-blocking* write. let result = this.write_to_host(stream, length, buffer_ptr)?; + // FIXME: When the host does a short write, we should emit an epoll edge -- at least for targets for which tokio assumes no short writes: + // match result { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::NotConnected => { // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK @@ -1578,6 +1557,8 @@ fn try_non_block_recv( length, buffer_ptr, )?; + // FIXME: When the host does a short read, we should emit an epoll edge -- at least for targets for which tokio assumes no short reads: + // match result { Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::NotConnected => { // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs index 64c1e8d4c3a6..c87677a756a0 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-socket.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-socket.rs @@ -11,7 +11,6 @@ use std::time::Duration; use libc_utils::*; -use utils::check_nondet; const TEST_BYTES: &[u8] = b"these are some test bytes!"; @@ -36,7 +35,6 @@ fn main() { test_accept_connect(); test_send_peek_recv(); - test_partial_send_recv(); test_write_read(); test_getsockname_ipv4(); @@ -295,50 +293,6 @@ fn test_send_peek_recv() { server_thread.join().unwrap(); } -/// Test that we actually do partial sends and partial receives for sockets. -fn test_partial_send_recv() { - let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); - let client_sockfd = - unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; - - // Spawn the server thread. - let server_thread = thread::spawn(move || { - let (peerfd, _) = net::accept_ipv4(server_sockfd).unwrap(); - - // Yield back to client to test that we do incomplete writes. - thread::sleep(Duration::from_millis(10)); - - // We know the buffer contains enough bytes to test incomplete reads. - - // Ensure we sometimes do incomplete reads. - check_nondet(|| { - let mut buffer = [0u8; 4]; - let bytes_read = - unsafe { errno_result(libc::read(peerfd, buffer.as_mut_ptr().cast(), 4)).unwrap() }; - bytes_read == 4 - }); - }); - - net::connect_ipv4(client_sockfd, addr).unwrap(); - - // Ensure we sometimes do incomplete writes. - check_nondet(|| { - let bytes_written = - unsafe { errno_result(libc::write(client_sockfd, [0; 4].as_ptr().cast(), 4)).unwrap() }; - bytes_written == 4 - }); - - let buffer = [0u8; 100_000]; - // Write a lot of bytes into the socket such that we can test - // incomplete reads. - unsafe { - errno_result(libc_utils::write_all(client_sockfd, buffer.as_ptr().cast(), buffer.len())) - .unwrap() - }; - - server_thread.join().unwrap(); -} - /// Test writing bytes into a connected stream and then reading them /// from the other end. /// We want to test this because `write` and `read` should be the same as From d49039c8e2eb19ecdb04f76e57248adf4a043aa1 Mon Sep 17 00:00:00 2001 From: Asuna Date: Thu, 23 Apr 2026 16:39:32 +0000 Subject: [PATCH 12/30] Use `AtomicUsize` instead of `AtomicBool` to test weak atomic Some architectures, such as RISC-V and LoongArch, lack support for native byte-sized atomic operations, so weak operations fallback to non-weak operations and are actually emulated by LL/SC loop, which never fail. --- src/tools/miri/tests/pass/atomic.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/tests/pass/atomic.rs b/src/tools/miri/tests/pass/atomic.rs index d8ac5114f27f..2231affc0c61 100644 --- a/src/tools/miri/tests/pass/atomic.rs +++ b/src/tools/miri/tests/pass/atomic.rs @@ -185,12 +185,12 @@ fn atomic_ptr() { } fn weak_sometimes_fails() { - let atomic = AtomicBool::new(false); + let atomic = AtomicUsize::new(0); let tries = 100; for _ in 0..tries { let cur = atomic.load(Relaxed); - // Try (weakly) to flip the flag. - if atomic.compare_exchange_weak(cur, !cur, Relaxed, Relaxed).is_err() { + // Try (weakly) to modify the flag. + if atomic.compare_exchange_weak(cur, cur + 1, Relaxed, Relaxed).is_err() { // We failed, so return and skip the panic. return; } From e917fdccb435d1f1a517c58a78c415e41634b9a9 Mon Sep 17 00:00:00 2001 From: dianne Date: Thu, 5 Mar 2026 10:26:20 -0800 Subject: [PATCH 13/30] add test --- tests/ui/pin/dont-deref-coerce-pinned-value.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/ui/pin/dont-deref-coerce-pinned-value.rs diff --git a/tests/ui/pin/dont-deref-coerce-pinned-value.rs b/tests/ui/pin/dont-deref-coerce-pinned-value.rs new file mode 100644 index 000000000000..d902b68ac492 --- /dev/null +++ b/tests/ui/pin/dont-deref-coerce-pinned-value.rs @@ -0,0 +1,13 @@ +//! Regression test for : when there's a type +//! expectation on `pin!`'s result, make sure we don't deref-coerce the argument to +//! `Pin::new_unchecked` to get its type to match up. That violates the pinning invariant, leading +//! to unsoundness! +//@ check-pass + +use std::pin::{Pin, pin}; + +fn wrong_pin(data: &mut T, callback: impl FnOnce(Pin<&mut T>)) { + callback(pin!(data)); +} + +fn main() {} From b4e013bbec9f42687345f152a685cd3518080289 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 23 Apr 2026 19:26:41 +0000 Subject: [PATCH 14/30] fix cstr clone_into unsoundness --- library/alloc/src/ffi/c_str.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/library/alloc/src/ffi/c_str.rs b/library/alloc/src/ffi/c_str.rs index b2f4277458f1..e6e6fcf5420f 100644 --- a/library/alloc/src/ffi/c_str.rs +++ b/library/alloc/src/ffi/c_str.rs @@ -1077,9 +1077,17 @@ fn to_owned(&self) -> CString { } fn clone_into(&self, target: &mut CString) { - let mut b = mem::take(&mut target.inner).into_vec(); - self.to_bytes_with_nul().clone_into(&mut b); - target.inner = b.into_boxed_slice(); + let src = self.to_bytes_with_nul(); + // If the lengths match, we can reuse the existing allocation without any overhead. + if target.inner.len() == src.len() { + target.inner.copy_from_slice(src); + } else { + // Reuse the existing allocation's capacity by converting to a Vec. + // We temporarily replace `target` with a valid dummy to remain panic-safe. + let mut b = mem::replace(&mut target.inner, Box::new([0])).into_vec(); + self.to_bytes_with_nul().clone_into(&mut b); + target.inner = b.into_boxed_slice(); + } } } From c96d970f2ddb4978627bb8ec23b33d64f8933274 Mon Sep 17 00:00:00 2001 From: Rudi Heitbaum Date: Fri, 24 Apr 2026 01:33:16 +0000 Subject: [PATCH 15/30] bump openssl-sys to support OpenSSL 4.0.x The previously pinned version of openssl-sys is not compatible with OpenSSL 4.0.x. - `openssl-sys`: 0.9.111 -> 0.9.114 --- src/tools/miri/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 25005693117e..3a810a6415d1 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -1013,9 +1013,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", From 5276fcd28eaaf3edbaa170990de622f3a9fe0bb1 Mon Sep 17 00:00:00 2001 From: dianne Date: Thu, 5 Mar 2026 10:28:12 -0800 Subject: [PATCH 16/30] prevent deref coercions in `pin!` --- library/core/src/lib.rs | 1 + library/core/src/pin.rs | 21 ++++++++++++-- .../iterators/iter-macro-not-async-closure.rs | 2 ++ .../iter-macro-not-async-closure.stderr | 28 +++++++++++++++++-- .../ui/pin/dont-deref-coerce-pinned-value.rs | 2 +- .../pin/dont-deref-coerce-pinned-value.stderr | 24 ++++++++++++++++ 6 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 tests/ui/pin/dont-deref-coerce-pinned-value.stderr diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 2b4ac66212e5..79694c46f2dc 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -157,6 +157,7 @@ #![feature(no_core)] #![feature(optimize_attribute)] #![feature(pattern_types)] +#![feature(pin_macro_internals)] #![feature(prelude_import)] #![feature(repr_simd)] #![feature(rustc_attrs)] diff --git a/library/core/src/pin.rs b/library/core/src/pin.rs index b65e40ef4675..f7224df849c8 100644 --- a/library/core/src/pin.rs +++ b/library/core/src/pin.rs @@ -2021,14 +2021,29 @@ unsafe impl PinCoerceUnsized for Pin {} /// [`Box::pin`]: ../../std/boxed/struct.Box.html#method.pin #[stable(feature = "pin_macro", since = "1.68.0")] #[rustc_macro_transparency = "semiopaque"] -#[allow_internal_unstable(super_let)] +#[allow_internal_unstable(pin_macro_internals, super_let)] #[rustc_diagnostic_item = "pin_macro"] // `super` gets removed by rustfmt #[rustfmt::skip] pub macro pin($value:expr $(,)?) { - { + 'p: { super let mut pinned = $value; // SAFETY: The value is pinned: it is the local above which cannot be named outside this macro. - unsafe { $crate::pin::Pin::new_unchecked(&mut pinned) } + break 'p unsafe { $crate::pin::Pin::new_unchecked(&mut pinned) }; + + // HACK: We need to ensure that, given `$value: T`, `pin!($value)` has type `Pin<&mut T>`. + // Otherwise, it's possible for a type annotation on the result of `pin!` to unsoundly add + // deref coercions. E.g. for `$value: &mut T`, we could get `pin!($value): Pin<&mut T>`, + // violating the pinning invariant; see . + #[expect(unreachable_code)] + $crate::pin::unreachable_pin_macro_type_constraint(pinned) } } + +/// Helper for `pin!` to enforce its type signature. +/// See . +#[unstable(feature = "pin_macro_internals", issue = "none")] +#[doc(hidden)] +pub fn unreachable_pin_macro_type_constraint<'a, T>(_: T) -> Pin<&'a mut T> { + unreachable!() +} diff --git a/tests/ui/iterators/iter-macro-not-async-closure.rs b/tests/ui/iterators/iter-macro-not-async-closure.rs index 634391883ea7..38ea33ccd773 100644 --- a/tests/ui/iterators/iter-macro-not-async-closure.rs +++ b/tests/ui/iterators/iter-macro-not-async-closure.rs @@ -27,6 +27,8 @@ fn main() { //~^^ ERROR AsyncFnOnce()` is not satisfied //~^^^ ERROR AsyncFnOnce()` is not satisfied //~^^^^ ERROR AsyncFnOnce()` is not satisfied + //~^^^^^ ERROR AsyncFnOnce()` is not satisfied + //~^^^^^^ ERROR AsyncFnOnce()` is not satisfied x.poll(&mut Context::from_waker(Waker::noop())); //~^ ERROR AsyncFnOnce()` is not satisfied } diff --git a/tests/ui/iterators/iter-macro-not-async-closure.stderr b/tests/ui/iterators/iter-macro-not-async-closure.stderr index 906ebd482fb6..735003207793 100644 --- a/tests/ui/iterators/iter-macro-not-async-closure.stderr +++ b/tests/ui/iterators/iter-macro-not-async-closure.stderr @@ -49,7 +49,31 @@ LL | async fn call_async_once(f: impl AsyncFnOnce()) { | ^^^^^^^^^^^^^ required by this bound in `call_async_once` error[E0277]: the trait bound `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}: AsyncFnOnce()` is not satisfied - --> $DIR/iter-macro-not-async-closure.rs:30:5 + --> $DIR/iter-macro-not-async-closure.rs:25:13 + | +LL | let x = pin!(call_async_once(f)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsyncFnOnce()` is not implemented for `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}` + | +note: required by a bound in `call_async_once` + --> $DIR/iter-macro-not-async-closure.rs:14:34 + | +LL | async fn call_async_once(f: impl AsyncFnOnce()) { + | ^^^^^^^^^^^^^ required by this bound in `call_async_once` + +error[E0277]: the trait bound `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}: AsyncFnOnce()` is not satisfied + --> $DIR/iter-macro-not-async-closure.rs:25:13 + | +LL | let x = pin!(call_async_once(f)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsyncFnOnce()` is not implemented for `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}` + | +note: required by a bound in `call_async_once` + --> $DIR/iter-macro-not-async-closure.rs:14:34 + | +LL | async fn call_async_once(f: impl AsyncFnOnce()) { + | ^^^^^^^^^^^^^ required by this bound in `call_async_once` + +error[E0277]: the trait bound `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}: AsyncFnOnce()` is not satisfied + --> $DIR/iter-macro-not-async-closure.rs:32:5 | LL | x.poll(&mut Context::from_waker(Waker::noop())); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsyncFnOnce()` is not implemented for `{gen closure@$DIR/iter-macro-not-async-closure.rs:19:21: 19:28}` @@ -60,6 +84,6 @@ note: required by a bound in `call_async_once` LL | async fn call_async_once(f: impl AsyncFnOnce()) { | ^^^^^^^^^^^^^ required by this bound in `call_async_once` -error: aborting due to 5 previous errors +error: aborting due to 7 previous errors For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/pin/dont-deref-coerce-pinned-value.rs b/tests/ui/pin/dont-deref-coerce-pinned-value.rs index d902b68ac492..02730b54f407 100644 --- a/tests/ui/pin/dont-deref-coerce-pinned-value.rs +++ b/tests/ui/pin/dont-deref-coerce-pinned-value.rs @@ -2,12 +2,12 @@ //! expectation on `pin!`'s result, make sure we don't deref-coerce the argument to //! `Pin::new_unchecked` to get its type to match up. That violates the pinning invariant, leading //! to unsoundness! -//@ check-pass use std::pin::{Pin, pin}; fn wrong_pin(data: &mut T, callback: impl FnOnce(Pin<&mut T>)) { callback(pin!(data)); + //~^ ERROR: mismatched types } fn main() {} diff --git a/tests/ui/pin/dont-deref-coerce-pinned-value.stderr b/tests/ui/pin/dont-deref-coerce-pinned-value.stderr new file mode 100644 index 000000000000..1d5afaa3ad71 --- /dev/null +++ b/tests/ui/pin/dont-deref-coerce-pinned-value.stderr @@ -0,0 +1,24 @@ +error[E0308]: mismatched types + --> $DIR/dont-deref-coerce-pinned-value.rs:9:14 + | +LL | fn wrong_pin(data: &mut T, callback: impl FnOnce(Pin<&mut T>)) { + | - expected this type parameter +LL | callback(pin!(data)); + | ^^^^^^^^^^ + | | + | expected type parameter `T`, found `&mut T` + | arguments to this function are incorrect + | + = note: expected type parameter `_` + found mutable reference `&mut _` +help: the return type of this call is `&mut T` due to the type of the argument passed + --> $DIR/dont-deref-coerce-pinned-value.rs:9:14 + | +LL | callback(pin!(data)); + | ^^^^^^^^^^ this argument influences the return type of `unreachable_pin_macro_type_constraint` +note: function defined here + --> $SRC_DIR/core/src/pin.rs:LL:COL + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From e509d19dc37e43a4f9e71a03685c6f3d2f878ac4 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Fri, 24 Apr 2026 05:43:10 +0000 Subject: [PATCH 17/30] Prepare for merging from rust-lang/rust This updates the rust-version file to 9836b06b55f5389f605ee7766eeecd9f17a86cb5. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index e9fc6c4cd023..5ab8e6c9d9ba 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -e22c616e4e87914135c1db261a03e0437255335e +9836b06b55f5389f605ee7766eeecd9f17a86cb5 From 058a8b9fa1a9a79422787cb3b1cab101dc0f4e14 Mon Sep 17 00:00:00 2001 From: enthropy7 <221884178+enthropy7@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:40:13 +0300 Subject: [PATCH 18/30] Support fstat on non-file-backed FDs --- src/tools/miri/src/shims/files.rs | 28 ++++- src/tools/miri/src/shims/unix/fs.rs | 84 ++++++++++---- .../miri/src/shims/unix/linux_like/epoll.rs | 10 +- .../miri/src/shims/unix/linux_like/eventfd.rs | 10 +- src/tools/miri/src/shims/unix/mod.rs | 2 +- .../miri/src/shims/unix/virtual_socket.rs | 13 ++- src/tools/miri/src/shims/windows/fs.rs | 6 +- .../pass-dep/libc/libc-fstat-non-file.rs | 108 ++++++++++++++++++ 8 files changed, 227 insertions(+), 34 deletions(-) create mode 100644 src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index 5468fd303742..d007853ead2f 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -1,6 +1,6 @@ use std::any::Any; use std::collections::BTreeMap; -use std::fs::{File, Metadata}; +use std::fs::File; use std::io::{ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write}; use std::marker::CoercePointee; use std::ops::Deref; @@ -9,7 +9,7 @@ use rustc_abi::Size; -use crate::shims::unix::UnixFileDescription; +use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// A unique id for file descriptions. While we could use the address, considering that @@ -209,10 +209,23 @@ fn destroy<'tcx>( throw_unsup_format!("cannot close {}", self.name()); } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + /// Returns the host `fs::Metadata` for this FD, if available. + /// Used by host-aware shims like Windows's `GetFileInformationByHandle`. + /// Unrelated to Unix `fstat`, which goes through `fstat()`. + fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors"); } + /// Return the metadata describing this FD for the `fstat`/`statx` family of syscalls. + /// File-backed FDs should call `FileMetadata::from_meta` with their host metadata. + /// Non-file-backed FDs should call `FileMetadata::synthetic` with an appropriate mode. + fn fstat<'tcx>( + &self, + _ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + throw_unsup_format!("fstat is not supported on {}", self.name()); + } + fn is_tty(&self, _communicate_allowed: bool) -> bool { // Most FDs are not tty's and the consequence of a wrong `false` are minor, // so we use a default impl here. @@ -432,10 +445,17 @@ fn destroy<'tcx>( } } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { interp_ok(self.file.metadata()) } + fn fstat<'tcx>( + &self, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + FileMetadata::from_meta(ecx, self.file.metadata()) + } + fn is_tty(&self, communicate_allowed: bool) -> bool { communicate_allowed && self.file.is_terminal() } diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index 5adc5932883e..d318d3cecdb5 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -229,17 +229,22 @@ fn write_stat_buf( let (access_sec, access_nsec) = metadata.accessed.unwrap_or((0, 0)); let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0)); let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0)); - let mode = metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?; // We do *not* use `deref_pointer_as` here since determining the right pointee type // is highly non-trivial: it depends on which exact alias of the function was invoked // (e.g. `fstat` vs `fstat64`), and then on FreeBSD it also depends on the ABI level // which can be different between the libc used by std and the libc used by everyone else. let buf = this.deref_pointer(buf_op)?; + + // `libc::S_IF*` constants are of type `mode_t`, which varies in width across targets + // (`u16` on macOS, `u32` on Linux). Read the scalar using `mode_t`'s size on the target. + let mode_t_size = this.libc_ty_layout("mode_t").size; + let mode: u32 = metadata.mode.to_uint(mode_t_size)?.try_into().unwrap(); + this.write_int_fields_named( &[ ("st_dev", metadata.dev.into()), - ("st_mode", mode.try_into().unwrap()), + ("st_mode", mode.into()), ("st_nlink", 0), ("st_ino", 0), ("st_uid", metadata.uid.into()), @@ -343,6 +348,34 @@ fn dir_entry_fields( } } +fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { + #[cfg(unix)] + use std::os::unix::fs::FileTypeExt; + + if file_type.is_file() { + "S_IFREG" + } else if file_type.is_dir() { + "S_IFDIR" + } else if file_type.is_symlink() { + "S_IFLNK" + } else { + // Certain file types are only available when the host is a Unix system. + #[cfg(unix)] + { + if file_type.is_socket() { + return "S_IFSOCK"; + } else if file_type.is_fifo() { + return "S_IFIFO"; + } else if file_type.is_char_device() { + return "S_IFCHR"; + } else if file_type.is_block_device() { + return "S_IFBLK"; + } + } + "S_IFREG" + } +} + impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn open( @@ -747,13 +780,12 @@ fn linux_statx( Err(err) => return this.set_last_error_and_return_i32(err), }; - // The `mode` field specifies the type of the file and the permissions over the file for - // the owner, its group and other users. Given that we can only provide the file type - // without using platform specific methods, we only set the bits corresponding to the file - // type. This should be an `__u16` but `libc` provides its values as `u32`. + // `statx.stx_mode` is `__u16`. `libc::S_IF*` are of type `mode_t`, which varies in + // width across targets (`u16` on macOS, `u32` on Linux). Read using `mode_t`'s size. + let mode_t_size = this.libc_ty_layout("mode_t").size; let mode: u16 = metadata .mode - .to_u32()? + .to_uint(mode_t_size)? .try_into() .unwrap_or_else(|_| bug!("libc contains bad value for constant")); @@ -1632,7 +1664,7 @@ fn extract_sec_and_nsec<'tcx>( /// Stores a file's metadata in order to avoid code duplication in the different metadata related /// shims. -struct FileMetadata { +pub struct FileMetadata { mode: Scalar, size: u64, created: Option<(u64, u32)>, @@ -1662,13 +1694,28 @@ fn from_fd_num<'tcx>( let Some(fd) = ecx.machine.fds.get(fd_num) else { return interp_ok(Err(LibcError("EBADF"))); }; - - let metadata = fd.metadata()?; - drop(fd); - FileMetadata::from_meta(ecx, metadata) + fd.fstat(ecx) } - fn from_meta<'tcx>( + pub(crate) fn synthetic<'tcx>( + ecx: &mut MiriInterpCx<'tcx>, + mode_name: &str, + size: u64, + ) -> InterpResult<'tcx, Result> { + let mode = ecx.eval_libc(mode_name); + interp_ok(Ok(FileMetadata { + mode, + size, + created: None, + accessed: None, + modified: None, + dev: 0, + uid: 0, + gid: 0, + })) + } + + pub(crate) fn from_meta<'tcx>( ecx: &mut MiriInterpCx<'tcx>, metadata: Result, ) -> InterpResult<'tcx, Result> { @@ -1680,16 +1727,7 @@ fn from_meta<'tcx>( }; let file_type = metadata.file_type(); - - let mode_name = if file_type.is_file() { - "S_IFREG" - } else if file_type.is_dir() { - "S_IFDIR" - } else { - "S_IFLNK" - }; - - let mode = ecx.eval_libc(mode_name); + let mode = ecx.eval_libc(file_type_to_mode_name(file_type)); let size = metadata.len(); diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs index 7480db00d6ed..48085fa6ae26 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -10,7 +10,7 @@ use crate::shims::files::{ DynFileDescriptionRef, FdId, FdNum, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; -use crate::shims::unix::UnixFileDescription; +use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; type EpollEventKey = (FdId, FdNum); @@ -119,6 +119,14 @@ fn name(&self) -> &'static str { "epoll" } + fn fstat<'tcx>( + &self, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + // On Linux, epoll is an "anonymous inode" reported as S_IFREG. + FileMetadata::synthetic(ecx, "S_IFREG", 0) + } + fn destroy<'tcx>( mut self, self_id: FdId, diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs index d374a1e75f72..03bac1e7270d 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -5,8 +5,8 @@ use crate::concurrency::VClock; use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; -use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; +use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// Maximum value that the eventfd counter can hold. @@ -37,6 +37,14 @@ fn name(&self) -> &'static str { "event" } + fn fstat<'tcx>( + &self, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + // On Linux, eventfd is an "anonymous inode" reported as S_IFREG. + FileMetadata::synthetic(ecx, "S_IFREG", 0) + } + fn destroy<'tcx>( self, _self_id: FdId, diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index c55a28bfa7b2..9e8fa2a12d49 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -19,7 +19,7 @@ // All the Unix-specific extension traits pub use self::env::{EvalContextExt as _, UnixEnvVars}; pub use self::fd::{EvalContextExt as _, UnixFileDescription}; -pub use self::fs::{DirTable, EvalContextExt as _}; +pub use self::fs::{DirTable, EvalContextExt as _, FileMetadata}; pub use self::linux_like::epoll::EpollInterestTable; pub use self::mem::EvalContextExt as _; pub use self::socket::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/unix/virtual_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs index 51092b80c1c6..16eba61c56ba 100644 --- a/src/tools/miri/src/shims/unix/virtual_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -12,8 +12,8 @@ use crate::shims::files::{ EvalContextExt as _, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; -use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; +use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// The maximum capacity of the socketpair buffer in bytes. @@ -83,6 +83,17 @@ fn name(&self) -> &'static str { } } + fn fstat<'tcx>( + &self, + ecx: &mut MiriInterpCx<'tcx>, + ) -> InterpResult<'tcx, Result> { + let mode_name = match self.fd_type { + VirtualSocketType::Socketpair => "S_IFSOCK", + VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO", + }; + FileMetadata::synthetic(ecx, mode_name, 0) + } + fn destroy<'tcx>( self, _self_id: FdId, diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index 1ee93cf911c5..ddbb2b1de018 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -22,7 +22,7 @@ fn name(&self) -> &'static str { "directory" } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { interp_ok(self.path.metadata()) } @@ -49,7 +49,7 @@ fn name(&self) -> &'static str { "metadata-only" } - fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { interp_ok(Ok(self.meta.clone())) } @@ -328,7 +328,7 @@ fn GetFileInformationByHandle( this.invalid_handle("GetFileInformationByHandle")? }; - let metadata = match desc.metadata()? { + let metadata = match desc.host_metadata()? { Ok(meta) => meta, Err(e) => { this.set_last_error(e)?; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs new file mode 100644 index 000000000000..c29c8ceaa1df --- /dev/null +++ b/src/tools/miri/tests/pass-dep/libc/libc-fstat-non-file.rs @@ -0,0 +1,108 @@ +//@ignore-target: windows # No libc fstat on non-file FDs on Windows +//@compile-flags: -Zmiri-disable-isolation + +use std::mem::MaybeUninit; + +#[path = "../../utils/libc.rs"] +mod libc_utils; +use libc_utils::errno_check; + +fn main() { + test_fstat_socketpair(); + test_fstat_pipe(); + #[cfg(target_os = "linux")] + test_fstat_eventfd(); + #[cfg(target_os = "linux")] + test_fstat_epoll(); +} + +/// Calls fstat and returns a reference to the result. +/// We use `assume_init_ref` rather than `assume_init` because not all fields +/// of `libc::stat` may be written by fstat (e.g. `st_lspare` on macOS). +fn do_fstat(fd: i32, buf: &mut MaybeUninit) -> &libc::stat { + let res = unsafe { libc::fstat(fd, buf.as_mut_ptr()) }; + assert_eq!(res, 0, "fstat failed on fd {}", fd); + unsafe { buf.assume_init_ref() } +} + +fn assert_stat_fields_are_accessible(stat: &libc::stat) { + let _st_nlink = stat.st_nlink; + let _st_blksize = stat.st_blksize; + let _st_blocks = stat.st_blocks; + let _st_ino = stat.st_ino; + let _st_dev = stat.st_dev; + let _st_uid = stat.st_uid; + let _st_gid = stat.st_gid; + let _st_rdev = stat.st_rdev; + let _st_atime = stat.st_atime; + let _st_mtime = stat.st_mtime; + let _st_ctime = stat.st_ctime; + let _st_atime_nsec = stat.st_atime_nsec; + let _st_mtime_nsec = stat.st_mtime_nsec; + let _st_ctime_nsec = stat.st_ctime_nsec; +} + +/// Test fstat on socketpair file descriptors. +fn test_fstat_socketpair() { + let mut fds = [0i32; 2]; + errno_check(unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }); + + for fd in fds.iter() { + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(*fd, &mut buf); + assert_eq!( + stat.st_mode & libc::S_IFMT, + libc::S_IFSOCK, + "socketpair should have S_IFSOCK mode" + ); + assert_eq!(stat.st_size, 0, "socketpair should have size 0"); + assert_stat_fields_are_accessible(stat); + } + + errno_check(unsafe { libc::close(fds[0]) }); + errno_check(unsafe { libc::close(fds[1]) }); +} + +/// Test fstat on pipe file descriptors. +fn test_fstat_pipe() { + let mut fds = [0i32; 2]; + errno_check(unsafe { libc::pipe(fds.as_mut_ptr()) }); + + for fd in fds.iter() { + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(*fd, &mut buf); + assert_eq!(stat.st_mode & libc::S_IFMT, libc::S_IFIFO, "pipe should have S_IFIFO mode"); + assert_eq!(stat.st_size, 0, "pipe should have size 0"); + assert_stat_fields_are_accessible(stat); + } + + errno_check(unsafe { libc::close(fds[0]) }); + errno_check(unsafe { libc::close(fds[1]) }); +} + +/// Test fstat on eventfd file descriptors (Linux only). +#[cfg(target_os = "linux")] +fn test_fstat_eventfd() { + let flags = libc::EFD_CLOEXEC | libc::EFD_NONBLOCK; + let fd = libc_utils::errno_result(unsafe { libc::eventfd(0, flags) }).unwrap(); + + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(fd, &mut buf); + assert_eq!(stat.st_size, 0, "eventfd should have size 0"); + assert_stat_fields_are_accessible(stat); + + errno_check(unsafe { libc::close(fd) }); +} + +/// Test fstat on epoll file descriptors (Linux only). +#[cfg(target_os = "linux")] +fn test_fstat_epoll() { + let fd = libc_utils::errno_result(unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) }).unwrap(); + + let mut buf = MaybeUninit::uninit(); + let stat = do_fstat(fd, &mut buf); + assert_eq!(stat.st_size, 0, "epoll should have size 0"); + assert_stat_fields_are_accessible(stat); + + errno_check(unsafe { libc::close(fd) }); +} From 29d58f8b4cba84f8641da66cdd7424549f3e0e83 Mon Sep 17 00:00:00 2001 From: human9000 Date: Sat, 25 Apr 2026 19:42:57 +0500 Subject: [PATCH 19/30] Utilize `if let` guards where aproppriate --- .../rustc_mir_transform/src/ssa_range_prop.rs | 99 +++++++++---------- 1 file changed, 48 insertions(+), 51 deletions(-) diff --git a/compiler/rustc_mir_transform/src/ssa_range_prop.rs b/compiler/rustc_mir_transform/src/ssa_range_prop.rs index 7a8be8efdfd1..97ba07d90096 100644 --- a/compiler/rustc_mir_transform/src/ssa_range_prop.rs +++ b/compiler/rustc_mir_transform/src/ssa_range_prop.rs @@ -143,14 +143,13 @@ fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) { fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) { self.super_statement(statement, location); match &statement.kind { - StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(operand)) => { + StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(operand)) if let Some(place) = operand.place() - && self.is_ssa(place) - { - let successor = location.successor_within_block(); - let range = WrappingRange { start: 1, end: 1 }; - self.insert_range(place, successor, range); - } + && self.is_ssa(place) => + { + let successor = location.successor_within_block(); + let range = WrappingRange { start: 1, end: 1 }; + self.insert_range(place, successor, range); } _ => {} } @@ -159,58 +158,56 @@ fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Locatio fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) { self.super_terminator(terminator, location); match &terminator.kind { - TerminatorKind::Assert { cond, expected, target, .. } => { + TerminatorKind::Assert { cond, expected, target, .. } if let Some(place) = cond.place() + && self.is_ssa(place) => + { + let successor = Location { block: *target, statement_index: 0 }; + if location.dominates(successor, &self.dominators) { + assert_ne!(location.block, successor.block); + let val = *expected as u128; + let range = WrappingRange { start: val, end: val }; + self.insert_range(place, successor, range); + } + } + TerminatorKind::SwitchInt { discr, targets } + if let Some(place) = discr.place() && self.is_ssa(place) - { - let successor = Location { block: *target, statement_index: 0 }; - if location.dominates(successor, &self.dominators) { + // Reduce the potential compile-time overhead. + && targets.all_targets().len() < 16 => + { + let mut distinct_targets: FxHashMap = FxHashMap::default(); + for (_, target) in targets.iter() { + let targets = distinct_targets.entry(target).or_default(); + *targets += 1; + } + for (val, target) in targets.iter() { + if distinct_targets[&target] != 1 { + // FIXME: For multiple targets, the range can be the union of their values. + continue; + } + let successor = Location { block: target, statement_index: 0 }; + if self.unique_predecessors.contains(successor.block) { assert_ne!(location.block, successor.block); - let val = *expected as u128; let range = WrappingRange { start: val, end: val }; self.insert_range(place, successor, range); } } - } - TerminatorKind::SwitchInt { discr, targets } => { - if let Some(place) = discr.place() - && self.is_ssa(place) - // Reduce the potential compile-time overhead. - && targets.all_targets().len() < 16 - { - let mut distinct_targets: FxHashMap = FxHashMap::default(); - for (_, target) in targets.iter() { - let targets = distinct_targets.entry(target).or_default(); - *targets += 1; - } - for (val, target) in targets.iter() { - if distinct_targets[&target] != 1 { - // FIXME: For multiple targets, the range can be the union of their values. - continue; - } - let successor = Location { block: target, statement_index: 0 }; - if self.unique_predecessors.contains(successor.block) { - assert_ne!(location.block, successor.block); - let range = WrappingRange { start: val, end: val }; - self.insert_range(place, successor, range); - } - } - // FIXME: The range for the otherwise target be extend to more types. - // For instance, `val` is within the range [4, 1) at the otherwise target of `matches!(val, 1 | 2 | 3)`. - let otherwise = Location { block: targets.otherwise(), statement_index: 0 }; - if place.ty(self.local_decls, self.tcx).ty.is_bool() - && let [val] = targets.all_values() - && self.unique_predecessors.contains(otherwise.block) - { - assert_ne!(location.block, otherwise.block); - let range = if val.get() == 0 { - WrappingRange { start: 1, end: 1 } - } else { - WrappingRange { start: 0, end: 0 } - }; - self.insert_range(place, otherwise, range); - } + // FIXME: The range for the otherwise target be extend to more types. + // For instance, `val` is within the range [4, 1) at the otherwise target of `matches!(val, 1 | 2 | 3)`. + let otherwise = Location { block: targets.otherwise(), statement_index: 0 }; + if place.ty(self.local_decls, self.tcx).ty.is_bool() + && let [val] = targets.all_values() + && self.unique_predecessors.contains(otherwise.block) + { + assert_ne!(location.block, otherwise.block); + let range = if val.get() == 0 { + WrappingRange { start: 1, end: 1 } + } else { + WrappingRange { start: 0, end: 0 } + }; + self.insert_range(place, otherwise, range); } } _ => {} From b6c6dd1fa08e144a0842c4d294f314634fe4a2be Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sat, 25 Apr 2026 19:56:05 +0200 Subject: [PATCH 20/30] macro_metavar_expr_concat: explain why idents are invalid --- compiler/rustc_expand/src/errors.rs | 43 +++++++ compiler/rustc_expand/src/mbe/transcribe.rs | 15 +-- .../concat-trace-errors.rs | 10 ++ .../concat-trace-errors.stderr | 6 +- .../concat-usage-errors.rs | 8 ++ .../concat-usage-errors.stderr | 109 +++++++++++------- 6 files changed, 139 insertions(+), 52 deletions(-) diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index 6c5732f497f8..d74b1787d83c 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -545,6 +545,49 @@ pub(crate) struct MveUnrecognizedVar { pub span: Span, pub key: MacroRulesNormalizedIdent, } + + #[derive(Diagnostic)] + #[diag(r#"`${"{"}concat(..){"}"}` is not generating a valid identifier"#)] + pub(crate) struct ConcatInvalidIdent { + #[primary_span] + pub span: Span, + #[subdiagnostic] + pub reason: InvalidIdentReason, + } + + #[derive(Subdiagnostic)] + pub(crate) enum InvalidIdentReason { + #[note(r#"this `${"{"}concat(..){"}"}` invocation generated an empty ident"#)] + Empty, + #[note(r#"this `${"{"}concat(..){"}"}` invocation generated `{$symbol}`, but {$start} is neither '_' nor XID_Start"#)] + #[note( + "see for the definition of valid identifiers" + )] + InvalidStart { symbol: Symbol, start: char }, + #[note(r#"this `${"{"}concat(..){"}"}` invocation generated `{$symbol}`, but {$not_continue} is not XID_Continue"#)] + #[note( + "see for the definition of valid identifiers" + )] + InvalidContinue { symbol: Symbol, not_continue: char }, + } + + impl InvalidIdentReason { + pub(crate) fn new(symbol: Symbol) -> Self { + let mut chars = symbol.as_str().chars(); + if let Some(start) = chars.next() { + if rustc_lexer::is_id_start(start) { + let not_continue = chars + .find(|c| !rustc_lexer::is_id_continue(*c)) + .expect("InvalidIdentReason: cannot find invalid ident reason"); + InvalidIdentReason::InvalidContinue { symbol, not_continue } + } else { + InvalidIdentReason::InvalidStart { symbol, start } + } + } else { + InvalidIdentReason::Empty + } + } + } } #[derive(Diagnostic)] diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index 7152ffcd9f30..c56c5569f9a5 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -18,9 +18,10 @@ use smallvec::{SmallVec, smallvec}; use crate::errors::{ - CountRepetitionMisplaced, MacroVarStillRepeating, MetaVarsDifSeqMatchers, MustRepeatOnce, - MveUnrecognizedVar, NoRepeatableVar, NoSyntaxVarsExprRepeat, VarNoTypo, - VarTypoSuggestionRepeatable, VarTypoSuggestionUnrepeatable, VarTypoSuggestionUnrepeatableLabel, + ConcatInvalidIdent, CountRepetitionMisplaced, InvalidIdentReason, MacroVarStillRepeating, + MetaVarsDifSeqMatchers, MustRepeatOnce, MveUnrecognizedVar, NoRepeatableVar, + NoSyntaxVarsExprRepeat, VarNoTypo, VarTypoSuggestionRepeatable, VarTypoSuggestionUnrepeatable, + VarTypoSuggestionUnrepeatableLabel, }; use crate::mbe::macro_parser::NamedMatch; use crate::mbe::macro_parser::NamedMatch::*; @@ -656,10 +657,10 @@ fn metavar_expr_concat<'tx>( let symbol = nfc_normalize(&concatenated); let concatenated_span = tscx.visited_dspan(dspan); if !rustc_lexer::is_ident(symbol.as_str()) { - return Err(dcx.struct_span_err( - concatenated_span, - "`${concat(..)}` is not generating a valid identifier", - )); + return Err(dcx.create_err(ConcatInvalidIdent { + span: concatenated_span, + reason: InvalidIdentReason::new(symbol), + })); } tscx.psess.symbol_gallery.insert(symbol, concatenated_span); diff --git a/tests/ui/macros/metavar-expressions/concat-trace-errors.rs b/tests/ui/macros/metavar-expressions/concat-trace-errors.rs index 45407f5e86d5..284c84378e4a 100644 --- a/tests/ui/macros/metavar-expressions/concat-trace-errors.rs +++ b/tests/ui/macros/metavar-expressions/concat-trace-errors.rs @@ -16,18 +16,28 @@ macro_rules! post_expansion { ($a:literal) => { const _: () = ${concat("hi", $a, "bye")}; //~^ ERROR is not generating a valid identifier + //~| NOTE this `${concat(..)}` invocation generated `hi!bye`, but '!' is not XID_Continue + //~| NOTE see for the definition of valid identifiers } } post_expansion!("!"); +//~^ NOTE in this expansion of post_expansion! +//~| NOTE in this expansion of post_expansion! +//~| NOTE in this expansion of post_expansion! macro_rules! post_expansion_many { ($a:ident, $b:ident, $c:ident, $d:literal, $e:ident) => { const _: () = ${concat($a, $b, $c, $d, $e)}; //~^ ERROR is not generating a valid identifier + //~| NOTE this `${concat(..)}` invocation generated `abc.de`, but '.' is not XID_Continue + //~| NOTE see for the definition of valid identifiers } } post_expansion_many!(a, b, c, ".d", e); +//~^ NOTE in this expansion of post_expansion_many! +//~| NOTE in this expansion of post_expansion_many! +//~| NOTE in this expansion of post_expansion_many! fn main() {} diff --git a/tests/ui/macros/metavar-expressions/concat-trace-errors.stderr b/tests/ui/macros/metavar-expressions/concat-trace-errors.stderr index dac8b58a15ce..a6fc67d415d9 100644 --- a/tests/ui/macros/metavar-expressions/concat-trace-errors.stderr +++ b/tests/ui/macros/metavar-expressions/concat-trace-errors.stderr @@ -7,10 +7,12 @@ LL | const _: () = ${concat("hi", $a, "bye")}; LL | post_expansion!("!"); | -------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `hi!bye`, but '!' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `post_expansion` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-trace-errors.rs:26:24 + --> $DIR/concat-trace-errors.rs:31:24 | LL | const _: () = ${concat($a, $b, $c, $d, $e)}; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -18,6 +20,8 @@ LL | const _: () = ${concat($a, $b, $c, $d, $e)}; LL | post_expansion_many!(a, b, c, ".d", e); | -------------------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `abc.de`, but '.' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `post_expansion_many` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 2 previous errors diff --git a/tests/ui/macros/metavar-expressions/concat-usage-errors.rs b/tests/ui/macros/metavar-expressions/concat-usage-errors.rs index 277ad240b1bf..c82c9b92da9f 100644 --- a/tests/ui/macros/metavar-expressions/concat-usage-errors.rs +++ b/tests/ui/macros/metavar-expressions/concat-usage-errors.rs @@ -1,4 +1,5 @@ //@ edition: 2021 +//@ dont-require-annotations: NOTE #![feature(macro_metavar_expr_concat)] @@ -43,6 +44,7 @@ macro_rules! starting_number { ($ident:ident) => {{ let ${concat("1", $ident)}: () = (); //~^ ERROR `${concat(..)}` is not generating a valid identifier + //~| NOTE this `${concat(..)}` invocation generated `1_abc`, but '1' is neither '_' nor XID_Start }}; } @@ -56,6 +58,8 @@ macro_rules! starting_invalid_unicode { ($ident:ident) => {{ let ${concat("\u{00BD}", $ident)}: () = (); //~^ ERROR `${concat(..)}` is not generating a valid identifier + //~| NOTE this `${concat(..)}` invocation generated `\u{00BD}_abc`, but '\' is neither '_' nor XID_Start + //~| NOTE see for the definition of valid identifiers }}; } @@ -75,6 +79,7 @@ macro_rules! ending_invalid_unicode { ($ident:ident) => {{ let ${concat($ident, "\u{00BD}")}: () = (); //~^ ERROR `${concat(..)}` is not generating a valid identifier + //~| NOTE this `${concat(..)}` invocation generated `_abc\u{00BD}`, but '\' is not XID_Continue }}; } @@ -82,6 +87,7 @@ macro_rules! empty { () => {{ let ${concat("", "")}: () = (); //~^ ERROR `${concat(..)}` is not generating a valid identifier + //~| NOTE this `${concat(..)}` invocation generated an empty ident }}; } @@ -130,6 +136,8 @@ macro_rules! bad_literal_string { //~| ERROR `${concat(..)}` is not generating a valid identifier //~| ERROR `${concat(..)}` is not generating a valid identifier //~| ERROR `${concat(..)}` is not generating a valid identifier + //~| NOTE this `${concat(..)}` invocation generated `_foo🤷`, but '🤷' is not XID_Continue + //~| NOTE this `${concat(..)}` invocation generated `_foo-1`, but '-' is not XID_Continue } } diff --git a/tests/ui/macros/metavar-expressions/concat-usage-errors.stderr b/tests/ui/macros/metavar-expressions/concat-usage-errors.stderr index c124b76cb781..2ba81551289d 100644 --- a/tests/ui/macros/metavar-expressions/concat-usage-errors.stderr +++ b/tests/ui/macros/metavar-expressions/concat-usage-errors.stderr @@ -1,131 +1,131 @@ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:7:10 + --> $DIR/concat-usage-errors.rs:8:10 | LL | ${concat()} | ^^^^^^^^^^ error: `concat` must have at least two elements - --> $DIR/concat-usage-errors.rs:10:11 + --> $DIR/concat-usage-errors.rs:11:11 | LL | ${concat(aaaa)} | ^^^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:13:10 + --> $DIR/concat-usage-errors.rs:14:10 | LL | ${concat(aaaa,)} | ^^^^^^^^^^^^^^^ error: expected comma - --> $DIR/concat-usage-errors.rs:18:10 + --> $DIR/concat-usage-errors.rs:19:10 | LL | ${concat(aaaa aaaa)} | ^^^^^^^^^^^^^^^^^^^ error: `concat` must have at least two elements - --> $DIR/concat-usage-errors.rs:21:11 + --> $DIR/concat-usage-errors.rs:22:11 | LL | ${concat($ex)} | ^^^^^^ error: expected comma - --> $DIR/concat-usage-errors.rs:27:10 + --> $DIR/concat-usage-errors.rs:28:10 | LL | ${concat($ex, aaaa 123)} | ^^^^^^^^^^^^^^^^^^^^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:30:10 + --> $DIR/concat-usage-errors.rs:31:10 | LL | ${concat($ex, aaaa,)} | ^^^^^^^^^^^^^^^^^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:90:26 + --> $DIR/concat-usage-errors.rs:96:26 | LL | let ${concat(_a, 'b')}: () = (); | ^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:93:26 + --> $DIR/concat-usage-errors.rs:99:26 | LL | let ${concat(_a, 1)}: () = (); | ^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:95:26 + --> $DIR/concat-usage-errors.rs:101:26 | LL | let ${concat(_a, 1.5)}: () = (); | ^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:97:26 + --> $DIR/concat-usage-errors.rs:103:26 | LL | let ${concat(_a, c"hi")}: () = (); | ^^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:99:26 + --> $DIR/concat-usage-errors.rs:105:26 | LL | let ${concat(_a, b"hi")}: () = (); | ^^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:101:26 + --> $DIR/concat-usage-errors.rs:107:26 | LL | let ${concat(_a, b'b')}: () = (); | ^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:103:26 + --> $DIR/concat-usage-errors.rs:109:26 | LL | let ${concat(_a, b'b')}: () = (); | ^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:106:30 + --> $DIR/concat-usage-errors.rs:112:30 | LL | let ${concat($ident, 'b')}: () = (); | ^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:108:30 + --> $DIR/concat-usage-errors.rs:114:30 | LL | let ${concat($ident, 1)}: () = (); | ^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:110:30 + --> $DIR/concat-usage-errors.rs:116:30 | LL | let ${concat($ident, 1.5)}: () = (); | ^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:112:30 + --> $DIR/concat-usage-errors.rs:118:30 | LL | let ${concat($ident, c"hi")}: () = (); | ^^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:114:30 + --> $DIR/concat-usage-errors.rs:120:30 | LL | let ${concat($ident, b"hi")}: () = (); | ^^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:116:30 + --> $DIR/concat-usage-errors.rs:122:30 | LL | let ${concat($ident, b'b')}: () = (); | ^^^^ error: expected identifier or string literal - --> $DIR/concat-usage-errors.rs:118:30 + --> $DIR/concat-usage-errors.rs:124:30 | LL | let ${concat($ident, b'b')}: () = (); | ^^^^ error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` - --> $DIR/concat-usage-errors.rs:24:19 + --> $DIR/concat-usage-errors.rs:25:19 | LL | ${concat($ex, aaaa)} | ^^ @@ -133,13 +133,13 @@ LL | ${concat($ex, aaaa)} = note: currently only string and integer literals are supported error: variable `foo` is not recognized in meta-variable expression - --> $DIR/concat-usage-errors.rs:37:30 + --> $DIR/concat-usage-errors.rs:38:30 | LL | const ${concat(FOO, $foo)}: i32 = 2; | ^^^ error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:44:14 + --> $DIR/concat-usage-errors.rs:45:14 | LL | let ${concat("1", $ident)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^ @@ -147,10 +147,12 @@ LL | let ${concat("1", $ident)}: () = (); LL | starting_number!(_abc); | ---------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `1_abc`, but '1' is neither '_' nor XID_Start + = note: see for the definition of valid identifiers = note: this error originates in the macro `starting_number` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:57:14 + --> $DIR/concat-usage-errors.rs:59:14 | LL | let ${concat("\u{00BD}", $ident)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -158,10 +160,12 @@ LL | let ${concat("\u{00BD}", $ident)}: () = (); LL | starting_invalid_unicode!(_abc); | ------------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `\u{00BD}_abc`, but '\' is neither '_' nor XID_Start + = note: see for the definition of valid identifiers = note: this error originates in the macro `starting_invalid_unicode` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:76:14 + --> $DIR/concat-usage-errors.rs:80:14 | LL | let ${concat($ident, "\u{00BD}")}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -169,10 +173,12 @@ LL | let ${concat($ident, "\u{00BD}")}: () = (); LL | ending_invalid_unicode!(_abc); | ----------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `_abc\u{00BD}`, but '\' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `ending_invalid_unicode` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected pattern, found `$` - --> $DIR/concat-usage-errors.rs:90:13 + --> $DIR/concat-usage-errors.rs:96:13 | LL | let ${concat(_a, 'b')}: () = (); | ^ expected pattern @@ -183,7 +189,7 @@ LL | unsupported_literals!(_abc); = note: this error originates in the macro `unsupported_literals` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:83:14 + --> $DIR/concat-usage-errors.rs:88:14 | LL | let ${concat("", "")}: () = (); | ^^^^^^^^^^^^^^^^ @@ -191,10 +197,11 @@ LL | let ${concat("", "")}: () = (); LL | empty!(); | -------- in this macro invocation | + = note: this `${concat(..)}` invocation generated an empty ident = note: this error originates in the macro `empty` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:125:16 + --> $DIR/concat-usage-errors.rs:131:16 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -202,10 +209,12 @@ LL | const ${concat(_foo, $literal)}: () = (); LL | bad_literal_string!("\u{00BD}"); | ------------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `_foo\u{00BD}`, but '\' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `bad_literal_string` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:125:16 + --> $DIR/concat-usage-errors.rs:131:16 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -213,10 +222,12 @@ LL | const ${concat(_foo, $literal)}: () = (); LL | bad_literal_string!("\x41"); | --------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `_foo\x41`, but '\' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `bad_literal_string` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:125:16 + --> $DIR/concat-usage-errors.rs:131:16 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -224,10 +235,12 @@ LL | const ${concat(_foo, $literal)}: () = (); LL | bad_literal_string!("🤷"); | ------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `_foo🤷`, but '🤷' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `bad_literal_string` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:125:16 + --> $DIR/concat-usage-errors.rs:131:16 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -235,10 +248,12 @@ LL | const ${concat(_foo, $literal)}: () = (); LL | bad_literal_string!("d[-_-]b"); | ------------------------------ in this macro invocation | + = note: this `${concat(..)}` invocation generated `_food[-_-]b`, but '[' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `bad_literal_string` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:125:16 + --> $DIR/concat-usage-errors.rs:131:16 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -246,10 +261,12 @@ LL | const ${concat(_foo, $literal)}: () = (); LL | bad_literal_string!("-1"); | ------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `_foo-1`, but '-' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `bad_literal_string` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:125:16 + --> $DIR/concat-usage-errors.rs:131:16 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -257,10 +274,12 @@ LL | const ${concat(_foo, $literal)}: () = (); LL | bad_literal_string!("1.0"); | -------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `_foo1.0`, but '.' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `bad_literal_string` (in Nightly builds, run with -Z macro-backtrace for more info) error: `${concat(..)}` is not generating a valid identifier - --> $DIR/concat-usage-errors.rs:125:16 + --> $DIR/concat-usage-errors.rs:131:16 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -268,10 +287,12 @@ LL | const ${concat(_foo, $literal)}: () = (); LL | bad_literal_string!("'1'"); | -------------------------- in this macro invocation | + = note: this `${concat(..)}` invocation generated `_foo'1'`, but '\'' is not XID_Continue + = note: see for the definition of valid identifiers = note: this error originates in the macro `bad_literal_string` (in Nightly builds, run with -Z macro-backtrace for more info) error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` - --> $DIR/concat-usage-errors.rs:138:31 + --> $DIR/concat-usage-errors.rs:146:31 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^ @@ -279,7 +300,7 @@ LL | const ${concat(_foo, $literal)}: () = (); = note: currently only string and integer literals are supported error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` - --> $DIR/concat-usage-errors.rs:138:31 + --> $DIR/concat-usage-errors.rs:146:31 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^ @@ -288,7 +309,7 @@ LL | const ${concat(_foo, $literal)}: () = (); = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` - --> $DIR/concat-usage-errors.rs:138:31 + --> $DIR/concat-usage-errors.rs:146:31 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^ @@ -297,7 +318,7 @@ LL | const ${concat(_foo, $literal)}: () = (); = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` - --> $DIR/concat-usage-errors.rs:138:31 + --> $DIR/concat-usage-errors.rs:146:31 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^ @@ -306,19 +327,19 @@ LL | const ${concat(_foo, $literal)}: () = (); = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: floats are not supported as metavariables of `${concat(..)}` - --> $DIR/concat-usage-errors.rs:138:31 + --> $DIR/concat-usage-errors.rs:146:31 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^ error: integer metavariables of `${concat(..)}` must not be suffixed - --> $DIR/concat-usage-errors.rs:138:31 + --> $DIR/concat-usage-errors.rs:146:31 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^ error: integer metavariables of `${concat(..)}` must not be suffixed - --> $DIR/concat-usage-errors.rs:138:31 + --> $DIR/concat-usage-errors.rs:146:31 | LL | const ${concat(_foo, $literal)}: () = (); | ^^^^^^^ @@ -326,7 +347,7 @@ LL | const ${concat(_foo, $literal)}: () = (); = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` - --> $DIR/concat-usage-errors.rs:151:31 + --> $DIR/concat-usage-errors.rs:159:31 | LL | const ${concat(_foo, $tt)}: () = (); | ^^ @@ -334,7 +355,7 @@ LL | const ${concat(_foo, $tt)}: () = (); = note: currently only string and integer literals are supported error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` - --> $DIR/concat-usage-errors.rs:151:31 + --> $DIR/concat-usage-errors.rs:159:31 | LL | const ${concat(_foo, $tt)}: () = (); | ^^ From f113540cec8632b6c8ab023c5175734ef96b5cb9 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sat, 25 Apr 2026 21:19:26 +0200 Subject: [PATCH 21/30] Remove unnecessary uses of `AttributeExt` --- compiler/rustc_ast/src/attr/mod.rs | 41 +++++++++++++++---- .../rustc_attr_parsing/src/attributes/util.rs | 7 ++-- compiler/rustc_resolve/src/rustdoc.rs | 2 +- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 369fe12539fa..bb2e444c722a 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -341,6 +341,34 @@ pub fn token_trees(&self) -> Vec { )], } } + + pub fn deprecation_note(&self) -> Option { + match &self.kind { + AttrKind::Normal(normal) if normal.item.path == sym::deprecated => { + let meta = &normal.item; + + // #[deprecated = "..."] + if let Some(s) = meta.value_str() { + return Some(Ident { name: s, span: meta.span() }); + } + + // #[deprecated(note = "...")] + if let Some(list) = meta.meta_item_list() { + for nested in list { + if let Some(mi) = nested.meta_item() + && mi.path == sym::note + && let Some(s) = mi.value_str() + { + return Some(Ident { name: s, span: mi.span }); + } + } + } + + None + } + _ => None, + } + } } impl AttrItem { @@ -824,19 +852,19 @@ pub fn mk_attr_name_value_str( mk_attr(g, style, unsafety, path, args, span) } -pub fn filter_by_name(attrs: &[A], name: Symbol) -> impl Iterator { +pub fn filter_by_name(attrs: &[Attribute], name: Symbol) -> impl Iterator { attrs.iter().filter(move |attr| attr.has_name(name)) } -pub fn find_by_name(attrs: &[A], name: Symbol) -> Option<&A> { +pub fn find_by_name(attrs: &[Attribute], name: Symbol) -> Option<&Attribute> { filter_by_name(attrs, name).next() } -pub fn first_attr_value_str_by_name(attrs: &[impl AttributeExt], name: Symbol) -> Option { +pub fn first_attr_value_str_by_name(attrs: &[Attribute], name: Symbol) -> Option { find_by_name(attrs, name).and_then(|attr| attr.value_str()) } -pub fn contains_name(attrs: &[impl AttributeExt], name: Symbol) -> bool { +pub fn contains_name(attrs: &[Attribute], name: Symbol) -> bool { find_by_name(attrs, name).is_some() } @@ -911,11 +939,6 @@ fn has_any_name(&self, names: &[Symbol]) -> bool { /// * `#[doc(...)]` returns `None`. fn doc_str(&self) -> Option; - /// Returns the deprecation note if this is deprecation attribute. - /// * `#[deprecated = "note"]` returns `Some("note")`. - /// * `#[deprecated(note = "note", ...)]` returns `Some("note")`. - fn deprecation_note(&self) -> Option; - /// Returns whether this attribute is any of the proc macro attributes. /// i.e. `proc_macro`, `proc_macro_attribute` or `proc_macro_derive`. fn is_proc_macro_attr(&self) -> bool { diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs index 98f8cc23b500..b6773df67cd0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/util.rs +++ b/compiler/rustc_attr_parsing/src/attributes/util.rs @@ -1,7 +1,6 @@ use std::num::IntErrorKind; -use rustc_ast::LitKind; -use rustc_ast::attr::AttributeExt; +use rustc_ast::{LitKind, ast}; use rustc_feature::is_builtin_attr_name; use rustc_hir::RustcVersion; use rustc_hir::limit::Limit; @@ -27,8 +26,8 @@ pub fn parse_version(s: Symbol) -> Option { Some(RustcVersion { major, minor, patch }) } -pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool { - attr.is_doc_comment().is_some() || attr.name().is_some_and(|name| is_builtin_attr_name(name)) +pub fn is_builtin_attr(attr: &ast::Attribute) -> bool { + attr.is_doc_comment() || attr.name().is_some_and(|name| is_builtin_attr_name(name)) } /// Parse a single integer. diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index 89b561bd2e90..f26405cb223c 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -409,7 +409,7 @@ pub fn may_be_doc_link(link_type: LinkType) -> bool { /// Simplified version of `preprocessed_markdown_links` from rustdoc. /// Must return at least the same links as it, but may add some more links on top of that. -pub(crate) fn attrs_to_preprocessed_links(attrs: &[A]) -> Vec> { +pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec> { let (doc_fragments, other_attrs) = attrs_to_doc_fragments(attrs.iter().map(|attr| (attr, None)), false); let mut doc = From e3b0e9db08425cf80090a193028ef49eae9761b1 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sat, 25 Apr 2026 21:26:00 +0200 Subject: [PATCH 22/30] Remove `deprecation_note` from `AttributeExt` --- compiler/rustc_ast/src/attr/mod.rs | 28 ---------------------------- compiler/rustc_hir/src/hir.rs | 8 -------- src/librustdoc/clean/types.rs | 7 +------ 3 files changed, 1 insertion(+), 42 deletions(-) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index bb2e444c722a..0374a86d3eb1 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -235,34 +235,6 @@ fn doc_str(&self) -> Option { } } - fn deprecation_note(&self) -> Option { - match &self.kind { - AttrKind::Normal(normal) if normal.item.path == sym::deprecated => { - let meta = &normal.item; - - // #[deprecated = "..."] - if let Some(s) = meta.value_str() { - return Some(Ident { name: s, span: meta.span() }); - } - - // #[deprecated(note = "...")] - if let Some(list) = meta.meta_item_list() { - for nested in list { - if let Some(mi) = nested.meta_item() - && mi.path == sym::note - && let Some(s) = mi.value_str() - { - return Some(Ident { name: s, span: mi.span }); - } - } - } - - None - } - _ => None, - } - } - fn doc_resolution_scope(&self) -> Option { match &self.kind { AttrKind::DocComment(..) => Some(self.style), diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 5608bd82fdac..60bfc5528256 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1415,14 +1415,6 @@ fn doc_str(&self) -> Option { } } - #[inline] - fn deprecation_note(&self) -> Option { - match &self { - Attribute::Parsed(AttributeKind::Deprecated { deprecation, .. }) => deprecation.note, - _ => None, - } - } - fn is_automatically_derived_attr(&self) -> bool { matches!(self, Attribute::Parsed(AttributeKind::AutomaticallyDerived(..))) } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index a1eb093d7fcf..e81c6eae1996 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -8,7 +8,6 @@ use itertools::Either; use rustc_abi::{ExternAbi, VariantIdx}; use rustc_ast as ast; -use rustc_ast::attr::AttributeExt; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; @@ -501,11 +500,7 @@ pub(crate) fn span(&self, tcx: TyCtxt<'_>) -> Option { } pub(crate) fn attr_span(&self, tcx: TyCtxt<'_>) -> rustc_span::Span { - let deprecation_notes = self - .attrs - .other_attrs - .iter() - .filter_map(|attr| attr.deprecation_note().map(|note| note.span)); + let deprecation_notes = find_attr!(&self.attrs.other_attrs, Deprecated { deprecation, .. } => deprecation.note.map(|note| note.span)).flatten(); span_of_fragments(&self.attrs.doc_strings) .into_iter() From efaf46022404ebecd9878afbc21f84ddf1dae1c4 Mon Sep 17 00:00:00 2001 From: Cheeshian Chuah Date: Sun, 26 Apr 2026 11:05:05 +0800 Subject: [PATCH 23/30] Mention DEPRECATED_LLVM_INTRINSIC lint for internal use --- compiler/rustc_lint_defs/src/builtin.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index b027872dd99c..68a220a7224d 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -5639,7 +5639,12 @@ /// LLVM periodically updates its list of intrinsics. Deprecated intrinsics are unlikely /// to be removed, but they may optimize less well than their new versions, so it's /// best to use the new version. Also, some deprecated intrinsics might have buggy - /// behavior + /// behavior. + /// + /// This `link_llvm_intrinsics` lint is intended to be used internally only, and requires the + /// `#![feature(link_llvm_intrinsics)]` internal feature gate. For more information, see [its chapter in + /// the Unstable Book](https://doc.rust-lang.org/unstable-book/language-features/link-llvm-intrinsics.html) + /// and [its tracking issue](https://github.com/rust-lang/rust/issues/29602). pub DEPRECATED_LLVM_INTRINSIC, Allow, "detects uses of deprecated LLVM intrinsics", From 79e10fd5efb26b9c49ae29a49385e2419375be2c Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:43:58 +0900 Subject: [PATCH 24/30] Remove the incomplete marker from `impl` restrictions --- compiler/rustc_feature/src/unstable.rs | 2 +- .../auxiliary/external-impl-restriction.rs | 1 - .../feature-gate-impl-restriction.rs | 1 - ...-gate-impl-restriction.without_gate.stderr | 34 ++++++++--------- .../impl-restriction-check.e2015.stderr | 32 ++++++++-------- .../impl-restriction-check.e2018.stderr | 32 ++++++++-------- .../impl-restriction-check.rs | 1 - .../recover-incorrect-impl-restriction.rs | 1 - .../recover-incorrect-impl-restriction.stderr | 14 +++---- .../restriction_resolution_errors.rs | 1 - .../restriction_resolution_errors.stderr | 38 +++++++++---------- .../trait-alias-cannot-be-impl-restricted.rs | 1 - ...ait-alias-cannot-be-impl-restricted.stderr | 24 ++++++------ 13 files changed, 88 insertions(+), 94 deletions(-) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index c175c3adc2f0..3bb4bc863def 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -549,7 +549,7 @@ pub fn internal(&self, feature: Symbol) -> bool { /// Target features on hexagon. (unstable, hexagon_target_feature, "1.27.0", Some(150250)), /// Allows `impl(crate) trait Foo` restrictions. - (incomplete, impl_restriction, "1.96.0", Some(105077)), + (unstable, impl_restriction, "CURRENT_RUSTC_VERSION", Some(105077)), /// Allows `impl Trait` to be used inside associated types (RFC 2515). (unstable, impl_trait_in_assoc_type, "1.70.0", Some(63063)), /// Allows `impl Trait` in bindings (`let`). diff --git a/tests/ui/impl-restriction/auxiliary/external-impl-restriction.rs b/tests/ui/impl-restriction/auxiliary/external-impl-restriction.rs index 785aeedf56d5..fa3814add9d3 100644 --- a/tests/ui/impl-restriction/auxiliary/external-impl-restriction.rs +++ b/tests/ui/impl-restriction/auxiliary/external-impl-restriction.rs @@ -1,5 +1,4 @@ #![feature(impl_restriction)] -#![expect(incomplete_features)] pub impl(crate) trait TopLevel {} diff --git a/tests/ui/impl-restriction/feature-gate-impl-restriction.rs b/tests/ui/impl-restriction/feature-gate-impl-restriction.rs index 737af7488c44..17b08b0ab723 100644 --- a/tests/ui/impl-restriction/feature-gate-impl-restriction.rs +++ b/tests/ui/impl-restriction/feature-gate-impl-restriction.rs @@ -3,7 +3,6 @@ //@[with_gate] check-pass #![cfg_attr(with_gate, feature(impl_restriction))] -#![cfg_attr(with_gate, allow(incomplete_features))] #![feature(auto_traits, const_trait_impl)] pub impl(crate) trait Bar {} //[without_gate]~ ERROR `impl` restrictions are experimental diff --git a/tests/ui/impl-restriction/feature-gate-impl-restriction.without_gate.stderr b/tests/ui/impl-restriction/feature-gate-impl-restriction.without_gate.stderr index d1f27350d1ba..b4cdcb3fba72 100644 --- a/tests/ui/impl-restriction/feature-gate-impl-restriction.without_gate.stderr +++ b/tests/ui/impl-restriction/feature-gate-impl-restriction.without_gate.stderr @@ -1,5 +1,5 @@ error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:9:5 + --> $DIR/feature-gate-impl-restriction.rs:8:5 | LL | pub impl(crate) trait Bar {} | ^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | pub impl(crate) trait Bar {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:10:5 + --> $DIR/feature-gate-impl-restriction.rs:9:5 | LL | pub impl(in crate) trait BarInCrate {} | ^^^^^^^^^^^^^^ @@ -19,7 +19,7 @@ LL | pub impl(in crate) trait BarInCrate {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:13:9 + --> $DIR/feature-gate-impl-restriction.rs:12:9 | LL | pub impl(in crate::foo) trait Baz {} | ^^^^^^^^^^^^^^^^^^^ @@ -29,7 +29,7 @@ LL | pub impl(in crate::foo) trait Baz {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:14:9 + --> $DIR/feature-gate-impl-restriction.rs:13:9 | LL | pub impl(super) unsafe trait BazUnsafeSuper {} | ^^^^^^^^^^^ @@ -39,7 +39,7 @@ LL | pub impl(super) unsafe trait BazUnsafeSuper {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:15:9 + --> $DIR/feature-gate-impl-restriction.rs:14:9 | LL | pub impl(self) auto trait BazAutoSelf {} | ^^^^^^^^^^ @@ -49,7 +49,7 @@ LL | pub impl(self) auto trait BazAutoSelf {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:16:9 + --> $DIR/feature-gate-impl-restriction.rs:15:9 | LL | pub impl(in self) const trait BazConst {} | ^^^^^^^^^^^^^ @@ -59,7 +59,7 @@ LL | pub impl(in self) const trait BazConst {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:19:13 + --> $DIR/feature-gate-impl-restriction.rs:18:13 | LL | pub impl(in crate::foo::foo_inner) trait Qux {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,7 +69,7 @@ LL | pub impl(in crate::foo::foo_inner) trait Qux {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:20:13 + --> $DIR/feature-gate-impl-restriction.rs:19:13 | LL | ... pub impl(in crate::foo::foo_inner) unsafe auto trait QuxAutoUnsafe {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -79,7 +79,7 @@ LL | ... pub impl(in crate::foo::foo_inner) unsafe auto trait QuxAutoUnsafe {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:21:13 + --> $DIR/feature-gate-impl-restriction.rs:20:13 | LL | ... pub impl(in crate::foo::foo_inner) const unsafe trait QuxConstUnsafe {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -89,7 +89,7 @@ LL | ... pub impl(in crate::foo::foo_inner) const unsafe trait QuxConstUnsafe = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:25:9 + --> $DIR/feature-gate-impl-restriction.rs:24:9 | LL | pub impl(crate) trait Bar {} | ^^^^^^^^^^^ @@ -99,7 +99,7 @@ LL | pub impl(crate) trait Bar {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:27:9 + --> $DIR/feature-gate-impl-restriction.rs:26:9 | LL | pub impl(in crate) trait BarInCrate {} | ^^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL | pub impl(in crate) trait BarInCrate {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:29:9 + --> $DIR/feature-gate-impl-restriction.rs:28:9 | LL | pub impl(self) unsafe trait BazUnsafeSelf {} | ^^^^^^^^^^ @@ -119,7 +119,7 @@ LL | pub impl(self) unsafe trait BazUnsafeSelf {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:31:9 + --> $DIR/feature-gate-impl-restriction.rs:30:9 | LL | pub impl(in super) auto trait BazAutoSuper {} | ^^^^^^^^^^^^^^ @@ -129,7 +129,7 @@ LL | pub impl(in super) auto trait BazAutoSuper {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:33:9 + --> $DIR/feature-gate-impl-restriction.rs:32:9 | LL | pub impl(super) const trait BazConstSuper {} | ^^^^^^^^^^^ @@ -139,7 +139,7 @@ LL | pub impl(super) const trait BazConstSuper {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:37:13 + --> $DIR/feature-gate-impl-restriction.rs:36:13 | LL | pub impl(in crate::foo::cfged_out_foo) trait CfgedOutQux {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -149,7 +149,7 @@ LL | pub impl(in crate::foo::cfged_out_foo) trait CfgedOutQux {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:38:13 + --> $DIR/feature-gate-impl-restriction.rs:37:13 | LL | ... pub impl(in crate::foo::cfged_out_foo) unsafe auto trait CfgedOutQuxUnsafeAuto {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -159,7 +159,7 @@ LL | ... pub impl(in crate::foo::cfged_out_foo) unsafe auto trait CfgedOutQuxU = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `impl` restrictions are experimental - --> $DIR/feature-gate-impl-restriction.rs:39:13 + --> $DIR/feature-gate-impl-restriction.rs:38:13 | LL | ... pub impl(in crate::foo::cfged_out_foo) const unsafe trait CfgedOutQuxConstUnsafe {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/impl-restriction/impl-restriction-check.e2015.stderr b/tests/ui/impl-restriction/impl-restriction-check.e2015.stderr index 402464f8a545..df7cf671bb69 100644 --- a/tests/ui/impl-restriction/impl-restriction-check.e2015.stderr +++ b/tests/ui/impl-restriction/impl-restriction-check.e2015.stderr @@ -1,95 +1,95 @@ error: trait cannot be implemented outside `external` - --> $DIR/impl-restriction-check.rs:12:1 + --> $DIR/impl-restriction-check.rs:11:1 | LL | impl external::TopLevel for LocalType {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/auxiliary/external-impl-restriction.rs:4:5 + --> $DIR/auxiliary/external-impl-restriction.rs:3:5 | LL | pub impl(crate) trait TopLevel {} | ^^^^^^^^^^^ error: trait cannot be implemented outside `external` - --> $DIR/impl-restriction-check.rs:13:1 + --> $DIR/impl-restriction-check.rs:12:1 | LL | impl external::inner::Inner for LocalType {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/auxiliary/external-impl-restriction.rs:7:9 + --> $DIR/auxiliary/external-impl-restriction.rs:6:9 | LL | pub impl(self) trait Inner {} | ^^^^^^^^^^ error: trait cannot be implemented outside `foo::bar` - --> $DIR/impl-restriction-check.rs:30:5 + --> $DIR/impl-restriction-check.rs:29:5 | LL | impl bar::Foo for i8 {} | ^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:17:20 + --> $DIR/impl-restriction-check.rs:16:20 | LL | pub(crate) impl(self) trait Foo {} | ^^^^^^^^^^ error: trait cannot be implemented outside `foo::bar` - --> $DIR/impl-restriction-check.rs:39:1 + --> $DIR/impl-restriction-check.rs:38:1 | LL | impl foo::bar::Foo for u8 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:17:20 + --> $DIR/impl-restriction-check.rs:16:20 | LL | pub(crate) impl(self) trait Foo {} | ^^^^^^^^^^ error: trait cannot be implemented outside `foo` - --> $DIR/impl-restriction-check.rs:41:1 + --> $DIR/impl-restriction-check.rs:40:1 | LL | impl foo::bar::Bar for u8 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:18:20 + --> $DIR/impl-restriction-check.rs:17:20 | LL | pub(crate) impl(super) trait Bar {} | ^^^^^^^^^^^ error: trait cannot be implemented outside `foo::bar` - --> $DIR/impl-restriction-check.rs:34:5 + --> $DIR/impl-restriction-check.rs:33:5 | LL | impl bar::Qux for i8 {} | ^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:20:20 + --> $DIR/impl-restriction-check.rs:19:20 | LL | pub(crate) impl(in crate::foo::bar) trait Qux {} | ^^^^^^^^^^^^^^^^^^^^^^^^ error: trait cannot be implemented outside `foo::bar` - --> $DIR/impl-restriction-check.rs:44:1 + --> $DIR/impl-restriction-check.rs:43:1 | LL | impl foo::bar::Qux for u8 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:20:20 + --> $DIR/impl-restriction-check.rs:19:20 | LL | pub(crate) impl(in crate::foo::bar) trait Qux {} | ^^^^^^^^^^^^^^^^^^^^^^^^ error: trait cannot be implemented outside `foo` - --> $DIR/impl-restriction-check.rs:46:1 + --> $DIR/impl-restriction-check.rs:45:1 | LL | impl foo::bar::FooBar for u8 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:21:20 + --> $DIR/impl-restriction-check.rs:20:20 | LL | pub(crate) impl(in crate::foo) trait FooBar {} | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/impl-restriction/impl-restriction-check.e2018.stderr b/tests/ui/impl-restriction/impl-restriction-check.e2018.stderr index 8bd256cc9cae..d1ef4fa4dc6c 100644 --- a/tests/ui/impl-restriction/impl-restriction-check.e2018.stderr +++ b/tests/ui/impl-restriction/impl-restriction-check.e2018.stderr @@ -1,95 +1,95 @@ error: trait cannot be implemented outside `external` - --> $DIR/impl-restriction-check.rs:12:1 + --> $DIR/impl-restriction-check.rs:11:1 | LL | impl external::TopLevel for LocalType {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/auxiliary/external-impl-restriction.rs:4:5 + --> $DIR/auxiliary/external-impl-restriction.rs:3:5 | LL | pub impl(crate) trait TopLevel {} | ^^^^^^^^^^^ error: trait cannot be implemented outside `external` - --> $DIR/impl-restriction-check.rs:13:1 + --> $DIR/impl-restriction-check.rs:12:1 | LL | impl external::inner::Inner for LocalType {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/auxiliary/external-impl-restriction.rs:7:9 + --> $DIR/auxiliary/external-impl-restriction.rs:6:9 | LL | pub impl(self) trait Inner {} | ^^^^^^^^^^ error: trait cannot be implemented outside `crate::foo::bar` - --> $DIR/impl-restriction-check.rs:30:5 + --> $DIR/impl-restriction-check.rs:29:5 | LL | impl bar::Foo for i8 {} | ^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:17:20 + --> $DIR/impl-restriction-check.rs:16:20 | LL | pub(crate) impl(self) trait Foo {} | ^^^^^^^^^^ error: trait cannot be implemented outside `crate::foo::bar` - --> $DIR/impl-restriction-check.rs:39:1 + --> $DIR/impl-restriction-check.rs:38:1 | LL | impl foo::bar::Foo for u8 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:17:20 + --> $DIR/impl-restriction-check.rs:16:20 | LL | pub(crate) impl(self) trait Foo {} | ^^^^^^^^^^ error: trait cannot be implemented outside `crate::foo` - --> $DIR/impl-restriction-check.rs:41:1 + --> $DIR/impl-restriction-check.rs:40:1 | LL | impl foo::bar::Bar for u8 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:18:20 + --> $DIR/impl-restriction-check.rs:17:20 | LL | pub(crate) impl(super) trait Bar {} | ^^^^^^^^^^^ error: trait cannot be implemented outside `crate::foo::bar` - --> $DIR/impl-restriction-check.rs:34:5 + --> $DIR/impl-restriction-check.rs:33:5 | LL | impl bar::Qux for i8 {} | ^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:20:20 + --> $DIR/impl-restriction-check.rs:19:20 | LL | pub(crate) impl(in crate::foo::bar) trait Qux {} | ^^^^^^^^^^^^^^^^^^^^^^^^ error: trait cannot be implemented outside `crate::foo::bar` - --> $DIR/impl-restriction-check.rs:44:1 + --> $DIR/impl-restriction-check.rs:43:1 | LL | impl foo::bar::Qux for u8 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:20:20 + --> $DIR/impl-restriction-check.rs:19:20 | LL | pub(crate) impl(in crate::foo::bar) trait Qux {} | ^^^^^^^^^^^^^^^^^^^^^^^^ error: trait cannot be implemented outside `crate::foo` - --> $DIR/impl-restriction-check.rs:46:1 + --> $DIR/impl-restriction-check.rs:45:1 | LL | impl foo::bar::FooBar for u8 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: trait restricted here - --> $DIR/impl-restriction-check.rs:21:20 + --> $DIR/impl-restriction-check.rs:20:20 | LL | pub(crate) impl(in crate::foo) trait FooBar {} | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/impl-restriction/impl-restriction-check.rs b/tests/ui/impl-restriction/impl-restriction-check.rs index cf5e699dfce9..f9403e218bd2 100644 --- a/tests/ui/impl-restriction/impl-restriction-check.rs +++ b/tests/ui/impl-restriction/impl-restriction-check.rs @@ -3,7 +3,6 @@ //@ [e2015] edition: 2015 //@ [e2018] edition: 2018.. #![feature(impl_restriction)] -#![expect(incomplete_features)] extern crate external_impl_restriction as external; diff --git a/tests/ui/impl-restriction/recover-incorrect-impl-restriction.rs b/tests/ui/impl-restriction/recover-incorrect-impl-restriction.rs index a11c7659ba78..6507ef54472b 100644 --- a/tests/ui/impl-restriction/recover-incorrect-impl-restriction.rs +++ b/tests/ui/impl-restriction/recover-incorrect-impl-restriction.rs @@ -1,5 +1,4 @@ #![feature(impl_restriction, auto_traits, const_trait_impl)] -#![expect(incomplete_features)] mod foo { pub impl(crate::foo) trait Baz {} //~ ERROR incorrect `impl` restriction diff --git a/tests/ui/impl-restriction/recover-incorrect-impl-restriction.stderr b/tests/ui/impl-restriction/recover-incorrect-impl-restriction.stderr index 23aff244bb71..aa01b6414412 100644 --- a/tests/ui/impl-restriction/recover-incorrect-impl-restriction.stderr +++ b/tests/ui/impl-restriction/recover-incorrect-impl-restriction.stderr @@ -1,5 +1,5 @@ error: incorrect `impl` restriction - --> $DIR/recover-incorrect-impl-restriction.rs:5:14 + --> $DIR/recover-incorrect-impl-restriction.rs:4:14 | LL | pub impl(crate::foo) trait Baz {} | ^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | pub impl(in crate::foo) trait Baz {} | ++ error: incorrect `impl` restriction - --> $DIR/recover-incorrect-impl-restriction.rs:6:14 + --> $DIR/recover-incorrect-impl-restriction.rs:5:14 | LL | pub impl(crate::foo) unsafe trait BazUnsafe {} | ^^^^^^^^^^ @@ -31,7 +31,7 @@ LL | pub impl(in crate::foo) unsafe trait BazUnsafe {} | ++ error: incorrect `impl` restriction - --> $DIR/recover-incorrect-impl-restriction.rs:7:14 + --> $DIR/recover-incorrect-impl-restriction.rs:6:14 | LL | pub impl(crate::foo) auto trait BazAuto {} | ^^^^^^^^^^ @@ -47,7 +47,7 @@ LL | pub impl(in crate::foo) auto trait BazAuto {} | ++ error: incorrect `impl` restriction - --> $DIR/recover-incorrect-impl-restriction.rs:8:14 + --> $DIR/recover-incorrect-impl-restriction.rs:7:14 | LL | pub impl(crate::foo) const trait BazConst {} | ^^^^^^^^^^ @@ -63,7 +63,7 @@ LL | pub impl(in crate::foo) const trait BazConst {} | ++ error: incorrect `impl` restriction - --> $DIR/recover-incorrect-impl-restriction.rs:9:14 + --> $DIR/recover-incorrect-impl-restriction.rs:8:14 | LL | pub impl(crate::foo) const unsafe trait BazConstUnsafe {} | ^^^^^^^^^^ @@ -79,7 +79,7 @@ LL | pub impl(in crate::foo) const unsafe trait BazConstUnsafe {} | ++ error: incorrect `impl` restriction - --> $DIR/recover-incorrect-impl-restriction.rs:10:14 + --> $DIR/recover-incorrect-impl-restriction.rs:9:14 | LL | pub impl(crate::foo) unsafe auto trait BazUnsafeAuto {} | ^^^^^^^^^^ @@ -95,7 +95,7 @@ LL | pub impl(in crate::foo) unsafe auto trait BazUnsafeAuto {} | ++ error: expected one of `for`, `where`, or `{`, found keyword `trait` - --> $DIR/recover-incorrect-impl-restriction.rs:17:33 + --> $DIR/recover-incorrect-impl-restriction.rs:16:33 | LL | pub unsafe impl(crate::foo) trait BadOrder1 {} | ^^^^^ expected one of `for`, `where`, or `{` diff --git a/tests/ui/impl-restriction/restriction_resolution_errors.rs b/tests/ui/impl-restriction/restriction_resolution_errors.rs index 01173c111ae7..5cc9f86bcaaa 100644 --- a/tests/ui/impl-restriction/restriction_resolution_errors.rs +++ b/tests/ui/impl-restriction/restriction_resolution_errors.rs @@ -1,6 +1,5 @@ //@ aux-build: external-impl-restriction.rs #![feature(impl_restriction)] -#![expect(incomplete_features)] extern crate external_impl_restriction as external; diff --git a/tests/ui/impl-restriction/restriction_resolution_errors.stderr b/tests/ui/impl-restriction/restriction_resolution_errors.stderr index 48bfab2bc1eb..019ad0eda824 100644 --- a/tests/ui/impl-restriction/restriction_resolution_errors.stderr +++ b/tests/ui/impl-restriction/restriction_resolution_errors.stderr @@ -1,77 +1,77 @@ error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:17:21 + --> $DIR/restriction_resolution_errors.rs:16:21 | LL | pub impl(in ::std) trait T2 {} | ^^^^^ error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:19:21 + --> $DIR/restriction_resolution_errors.rs:18:21 | LL | pub impl(in self::c) trait T3 {} | ^^^^^^^ error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:21:21 + --> $DIR/restriction_resolution_errors.rs:20:21 | LL | pub impl(in super::d) trait T4 {} | ^^^^^^^^ error[E0433]: too many leading `super` keywords - --> $DIR/restriction_resolution_errors.rs:27:35 + --> $DIR/restriction_resolution_errors.rs:26:35 | LL | pub impl(in super::super::super) trait T7 {} | ^^^^^ there are too many leading `super` keywords error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:37:21 + --> $DIR/restriction_resolution_errors.rs:36:21 | LL | pub impl(in self::f) trait L1 {} | ^^^^^^^ error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:41:21 + --> $DIR/restriction_resolution_errors.rs:40:21 | LL | pub impl(in super::h) trait L3 {} | ^^^^^^^^ error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:50:13 + --> $DIR/restriction_resolution_errors.rs:49:13 | LL | pub impl(in crate::a) trait T13 {} | ^^^^^^^^ error[E0433]: too many leading `super` keywords - --> $DIR/restriction_resolution_errors.rs:57:10 + --> $DIR/restriction_resolution_errors.rs:56:10 | LL | pub impl(super) trait T17 {} | ^^^^^ there are too many leading `super` keywords error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:59:13 + --> $DIR/restriction_resolution_errors.rs:58:13 | LL | pub impl(in external) trait T18 {} | ^^^^^^^^ error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:62:13 + --> $DIR/restriction_resolution_errors.rs:61:13 | LL | pub impl(in crate::j) trait L4 {} | ^^^^^^^^ error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:78:21 + --> $DIR/restriction_resolution_errors.rs:77:21 | LL | pub impl(in crate::m2) trait U2 {} | ^^^^^^^^^ error: trait implementation can only be restricted to ancestor modules - --> $DIR/restriction_resolution_errors.rs:80:21 + --> $DIR/restriction_resolution_errors.rs:79:21 | LL | pub impl(in m6::m5) trait U4 {} | ^^^^^^ error[E0433]: cannot find module or crate `a` in this scope - --> $DIR/restriction_resolution_errors.rs:15:21 + --> $DIR/restriction_resolution_errors.rs:14:21 | LL | pub impl(in a::b) trait T1 {} | ^ use of unresolved module or unlinked crate `a` @@ -87,25 +87,25 @@ LL + use a; | error[E0433]: cannot find module `c` in the crate root - --> $DIR/restriction_resolution_errors.rs:23:28 + --> $DIR/restriction_resolution_errors.rs:22:28 | LL | pub impl(in crate::c) trait T5 {} | ^ not found in the crate root error[E0577]: expected module, found enum `super::E` - --> $DIR/restriction_resolution_errors.rs:25:21 + --> $DIR/restriction_resolution_errors.rs:24:21 | LL | pub impl(in super::E) trait T6 {} | ^^^^^^^^ not a module error[E0577]: expected module, found enum `super::G` - --> $DIR/restriction_resolution_errors.rs:39:21 + --> $DIR/restriction_resolution_errors.rs:38:21 | LL | pub impl(in super::G) trait L2 {} | ^^^^^^^^ not a module error[E0577]: expected module, found enum `crate::a::E` - --> $DIR/restriction_resolution_errors.rs:52:13 + --> $DIR/restriction_resolution_errors.rs:51:13 | LL | pub mod b { | --------- similarly named module `b` defined here @@ -120,7 +120,7 @@ LL + pub impl(in crate::a::b) trait T14 {} | error[E0577]: expected module, found enum `crate::I` - --> $DIR/restriction_resolution_errors.rs:64:13 + --> $DIR/restriction_resolution_errors.rs:63:13 | LL | pub mod a { | --------- similarly named module `a` defined here @@ -135,7 +135,7 @@ LL + pub impl(in crate::a) trait L5 {} | error[E0577]: expected module, found enum `m7` - --> $DIR/restriction_resolution_errors.rs:81:21 + --> $DIR/restriction_resolution_errors.rs:80:21 | LL | pub impl(in m7) trait U5 {} | ^^ not a module diff --git a/tests/ui/impl-restriction/trait-alias-cannot-be-impl-restricted.rs b/tests/ui/impl-restriction/trait-alias-cannot-be-impl-restricted.rs index d191950ec40e..468fb1f6d290 100644 --- a/tests/ui/impl-restriction/trait-alias-cannot-be-impl-restricted.rs +++ b/tests/ui/impl-restriction/trait-alias-cannot-be-impl-restricted.rs @@ -1,5 +1,4 @@ #![feature(impl_restriction, auto_traits, const_trait_impl, trait_alias)] -#![expect(incomplete_features)] impl(crate) trait Alias = Copy; //~ ERROR trait aliases cannot be `impl`-restricted impl(in crate) auto trait AutoAlias = Copy; //~ ERROR trait aliases cannot be `impl`-restricted diff --git a/tests/ui/impl-restriction/trait-alias-cannot-be-impl-restricted.stderr b/tests/ui/impl-restriction/trait-alias-cannot-be-impl-restricted.stderr index def89574b041..d8978dc94cdb 100644 --- a/tests/ui/impl-restriction/trait-alias-cannot-be-impl-restricted.stderr +++ b/tests/ui/impl-restriction/trait-alias-cannot-be-impl-restricted.stderr @@ -1,71 +1,71 @@ error: trait aliases cannot be `impl`-restricted - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:4:1 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:3:1 | LL | impl(crate) trait Alias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `impl`-restricted error: trait aliases cannot be `auto` - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:5:1 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:4:1 | LL | impl(in crate) auto trait AutoAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `auto` error: trait aliases cannot be `impl`-restricted - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:5:1 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:4:1 | LL | impl(in crate) auto trait AutoAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `impl`-restricted error: trait aliases cannot be `unsafe` - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:7:1 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:6:1 | LL | impl(self) unsafe trait UnsafeAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `unsafe` error: trait aliases cannot be `impl`-restricted - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:7:1 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:6:1 | LL | impl(self) unsafe trait UnsafeAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `impl`-restricted error: trait aliases cannot be `impl`-restricted - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:9:1 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:8:1 | LL | impl(in self) const trait ConstAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `impl`-restricted error: trait aliases cannot be `impl`-restricted - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:12:5 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:11:5 | LL | impl(super) trait InnerAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `impl`-restricted error: trait aliases cannot be `unsafe` - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:13:5 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:12:5 | LL | impl(in crate::foo) const unsafe trait InnerConstUnsafeAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `unsafe` error: trait aliases cannot be `impl`-restricted - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:13:5 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:12:5 | LL | impl(in crate::foo) const unsafe trait InnerConstUnsafeAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `impl`-restricted error: trait aliases cannot be `auto` - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:15:5 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:14:5 | LL | impl(in crate::foo) unsafe auto trait InnerUnsafeAutoAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `auto` error: trait aliases cannot be `unsafe` - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:15:5 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:14:5 | LL | impl(in crate::foo) unsafe auto trait InnerUnsafeAutoAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `unsafe` error: trait aliases cannot be `impl`-restricted - --> $DIR/trait-alias-cannot-be-impl-restricted.rs:15:5 + --> $DIR/trait-alias-cannot-be-impl-restricted.rs:14:5 | LL | impl(in crate::foo) unsafe auto trait InnerUnsafeAutoAlias = Copy; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ trait aliases cannot be `impl`-restricted From 2c16f9edf5633bc9dc791b6faf65402aba277f4b Mon Sep 17 00:00:00 2001 From: cclfmht Date: Sun, 26 Apr 2026 10:50:05 +0000 Subject: [PATCH 25/30] Suggest enclosing format string with `""` under special cases * Suggest enclosing format string under special cases This commit add suggestions about enclosing format string when it falls into the following cases: `{}`, `{:?}`, `{:#?}`. * Add HELP annotations in the UI test --- compiler/rustc_builtin_macros/src/format.rs | 26 ++++++- ...y-block-unit-tuple-suggestion-130170.fixed | 4 +- ...-block-unit-tuple-suggestion-130170.stderr | 10 +++ .../macros/suggest-enclosing-format-string.rs | 27 +++++++ .../suggest-enclosing-format-string.stderr | 72 +++++++++++++++++++ 5 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 tests/ui/macros/suggest-enclosing-format-string.rs create mode 100644 tests/ui/macros/suggest-enclosing-format-string.stderr diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 7d01868645a0..8e055c855c4f 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -173,6 +173,16 @@ fn make_format_args( style: fmt_style, uncooked_symbol: uncooked_fmt_str, } = { + // Extract snippet so that we can check cases `{}`, `{:?}` and `{:#?}` and emit help for + // them later. + let snippet = if let ExprKind::Block(b, None) = &efmt.kind + && b.stmts.len() <= 1 + { + Some(ecx.sess.source_map().span_to_snippet(unexpanded_fmt_span)) + } else { + None + }; + let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, efmt.clone(), msg) else { return ExpandResult::Retry(()); }; @@ -222,12 +232,26 @@ fn make_format_args( }); } sugg_fmt = sugg_fmt.trim_end().to_string(); - err.span_suggestion( + err.span_suggestion_verbose( unexpanded_fmt_span.shrink_to_lo(), "you might be missing a string literal to format with", format!("\"{sugg_fmt}\", "), Applicability::MaybeIncorrect, ); + + if let Some(Ok(snippet)) = snippet.as_ref() { + match snippet.as_str() { + "{}" | "{:?}" | "{:#?}" => { + err.span_suggestion_verbose( + unexpanded_fmt_span, + format!("you might want to enclose `{snippet}` with `\"\"`"), + format!("\"{snippet}\""), + Applicability::MaybeIncorrect, + ); + } + _ => {} + }; + } } } err.emit() diff --git a/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.fixed b/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.fixed index 1ca5125fe8bc..e7b443470b6c 100644 --- a/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.fixed +++ b/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.fixed @@ -2,9 +2,9 @@ fn main() { let s = "123"; - println!("{:?} {} {}", {}, "sss", s); + println!("{:?} {} {}", "{}", "sss", s); //~^ ERROR format argument must be a string literal - println!("{:?}", {}); + println!("{:?}", "{}"); //~^ ERROR format argument must be a string literal println!("{} {} {} {:?}", s, "sss", s, {}); //~^ ERROR format argument must be a string literal diff --git a/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.stderr b/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.stderr index 81fca8c03cc1..8a64db9218f3 100644 --- a/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.stderr +++ b/tests/ui/macros/format-empty-block-unit-tuple-suggestion-130170.stderr @@ -8,6 +8,11 @@ help: you might be missing a string literal to format with | LL | println!("{:?} {} {}", {}, "sss", s); | +++++++++++++ +help: you might want to enclose `{}` with `""` + | +LL - println!({}, "sss", s); +LL + println!("{}", "sss", s); + | error: format argument must be a string literal --> $DIR/format-empty-block-unit-tuple-suggestion-130170.rs:7:14 @@ -19,6 +24,11 @@ help: you might be missing a string literal to format with | LL | println!("{:?}", {}); | +++++++ +help: you might want to enclose `{}` with `""` + | +LL - println!({}); +LL + println!("{}"); + | error: format argument must be a string literal --> $DIR/format-empty-block-unit-tuple-suggestion-130170.rs:9:14 diff --git a/tests/ui/macros/suggest-enclosing-format-string.rs b/tests/ui/macros/suggest-enclosing-format-string.rs new file mode 100644 index 000000000000..ab1c6d2a1a19 --- /dev/null +++ b/tests/ui/macros/suggest-enclosing-format-string.rs @@ -0,0 +1,27 @@ +// Suggest enclosing the format string with `""` when it is one of `{}`, `{:?}`, and `{:#?}`. + +#[derive(Debug)] +enum UwU { + QwQ, + AwA, + QAQ, +} + +fn main() { + println!({}, UwU::QwQ); + //~^ ERROR format argument must be a string literal + //~| HELP you might be missing a string literal to format with + //~| HELP you might want to enclose `{}` with `""` + println!({:?}, UwU::QwQ); + //~^ ERROR expected expression, found `:` + //~| ERROR format argument must be a string literal + //~| HELP you might be missing a string literal to format with + //~| HELP maybe write a path separator here + //~| HELP you might want to enclose `{:?}` with `""` + println!({:#?}, UwU::QwQ); + //~^ ERROR expected expression, found `:` + //~| ERROR format argument must be a string literal + //~| HELP you might be missing a string literal to format with + //~| HELP maybe write a path separator here + //~| HELP you might want to enclose `{:#?}` with `""` +} diff --git a/tests/ui/macros/suggest-enclosing-format-string.stderr b/tests/ui/macros/suggest-enclosing-format-string.stderr new file mode 100644 index 000000000000..64f46d39af34 --- /dev/null +++ b/tests/ui/macros/suggest-enclosing-format-string.stderr @@ -0,0 +1,72 @@ +error: format argument must be a string literal + --> $DIR/suggest-enclosing-format-string.rs:11:14 + | +LL | println!({}, UwU::QwQ); + | ^^ + | +help: you might be missing a string literal to format with + | +LL | println!("{:?} {}", {}, UwU::QwQ); + | ++++++++++ +help: you might want to enclose `{}` with `""` + | +LL - println!({}, UwU::QwQ); +LL + println!("{}", UwU::QwQ); + | + +error: expected expression, found `:` + --> $DIR/suggest-enclosing-format-string.rs:15:15 + | +LL | println!({:?}, UwU::QwQ); + | ^ expected expression + | +help: maybe write a path separator here + | +LL | println!({::?}, UwU::QwQ); + | + + +error: format argument must be a string literal + --> $DIR/suggest-enclosing-format-string.rs:15:14 + | +LL | println!({:?}, UwU::QwQ); + | ^^^^ + | +help: you might be missing a string literal to format with + | +LL | println!("{} {}", {:?}, UwU::QwQ); + | ++++++++ +help: you might want to enclose `{:?}` with `""` + | +LL - println!({:?}, UwU::QwQ); +LL + println!("{:?}", UwU::QwQ); + | + +error: expected expression, found `:` + --> $DIR/suggest-enclosing-format-string.rs:21:15 + | +LL | println!({:#?}, UwU::QwQ); + | ^ expected expression + | +help: maybe write a path separator here + | +LL | println!({::#?}, UwU::QwQ); + | + + +error: format argument must be a string literal + --> $DIR/suggest-enclosing-format-string.rs:21:14 + | +LL | println!({:#?}, UwU::QwQ); + | ^^^^^ + | +help: you might be missing a string literal to format with + | +LL | println!("{} {}", {:#?}, UwU::QwQ); + | ++++++++ +help: you might want to enclose `{:#?}` with `""` + | +LL - println!({:#?}, UwU::QwQ); +LL + println!("{:#?}", UwU::QwQ); + | + +error: aborting due to 5 previous errors + From c91a363db10281bf55e0e4a0dd88e5b04f924fb7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 26 Apr 2026 12:56:45 +0200 Subject: [PATCH 26/30] merge fstat and metadata functions --- src/tools/miri/src/lib.rs | 5 +- src/tools/miri/src/shims/files.rs | 32 +++------ src/tools/miri/src/shims/unix/fs.rs | 70 ++++++++++--------- .../miri/src/shims/unix/linux_like/epoll.rs | 9 ++- .../miri/src/shims/unix/linux_like/eventfd.rs | 9 ++- src/tools/miri/src/shims/unix/mod.rs | 2 +- .../miri/src/shims/unix/virtual_socket.rs | 9 ++- src/tools/miri/src/shims/windows/fs.rs | 22 ++++-- 8 files changed, 75 insertions(+), 83 deletions(-) diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index f41e3c20a7d5..5f3bf4a9e9e3 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -105,8 +105,9 @@ // Resolve ambiguity. #[doc(no_inline)] pub use rustc_const_eval::interpret::{self, AllocMap, Provenance as _}; -use rustc_log::tracing::{self, info, trace}; -use rustc_middle::{bug, span_bug}; +pub use rustc_data_structures::either::Either; +pub use rustc_log::tracing::{self, info, trace}; +pub use rustc_middle::{bug, span_bug}; #[cfg(all(feature = "native-lib", unix))] pub mod native_lib { diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs index d007853ead2f..04b84e6f3e67 100644 --- a/src/tools/miri/src/shims/files.rs +++ b/src/tools/miri/src/shims/files.rs @@ -9,7 +9,7 @@ use rustc_abi::Size; -use crate::shims::unix::{FileMetadata, UnixFileDescription}; +use crate::shims::unix::UnixFileDescription; use crate::*; /// A unique id for file descriptions. While we could use the address, considering that @@ -209,23 +209,14 @@ fn destroy<'tcx>( throw_unsup_format!("cannot close {}", self.name()); } - /// Returns the host `fs::Metadata` for this FD, if available. - /// Used by host-aware shims like Windows's `GetFileInformationByHandle`. - /// Unrelated to Unix `fstat`, which goes through `fstat()`. - fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { + /// Returns the metadata for this FD, if available. + /// This is either host metadata, or a non-file-backed-FD type. + /// The latter is for new represented as a string storing a `libc` name so we only + /// support that kind of metadata on Unix targets. + fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either, &'static str>> { throw_unsup_format!("obtaining metadata is only supported on file-backed file descriptors"); } - /// Return the metadata describing this FD for the `fstat`/`statx` family of syscalls. - /// File-backed FDs should call `FileMetadata::from_meta` with their host metadata. - /// Non-file-backed FDs should call `FileMetadata::synthetic` with an appropriate mode. - fn fstat<'tcx>( - &self, - _ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { - throw_unsup_format!("fstat is not supported on {}", self.name()); - } - fn is_tty(&self, _communicate_allowed: bool) -> bool { // Most FDs are not tty's and the consequence of a wrong `false` are minor, // so we use a default impl here. @@ -445,15 +436,8 @@ fn destroy<'tcx>( } } - fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(self.file.metadata()) - } - - fn fstat<'tcx>( - &self, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { - FileMetadata::from_meta(ecx, self.file.metadata()) + fn metadata<'tcx>(&self) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(self.file.metadata())) } fn is_tty(&self, communicate_allowed: bool) -> bool { diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index d318d3cecdb5..0988edaef2b0 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -348,34 +348,6 @@ fn dir_entry_fields( } } -fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { - #[cfg(unix)] - use std::os::unix::fs::FileTypeExt; - - if file_type.is_file() { - "S_IFREG" - } else if file_type.is_dir() { - "S_IFDIR" - } else if file_type.is_symlink() { - "S_IFLNK" - } else { - // Certain file types are only available when the host is a Unix system. - #[cfg(unix)] - { - if file_type.is_socket() { - return "S_IFSOCK"; - } else if file_type.is_fifo() { - return "S_IFIFO"; - } else if file_type.is_char_device() { - return "S_IFCHR"; - } else if file_type.is_block_device() { - return "S_IFBLK"; - } - } - "S_IFREG" - } -} - impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn open( @@ -1662,9 +1634,37 @@ fn extract_sec_and_nsec<'tcx>( } } +fn file_type_to_mode_name(file_type: std::fs::FileType) -> &'static str { + #[cfg(unix)] + use std::os::unix::fs::FileTypeExt; + + if file_type.is_file() { + "S_IFREG" + } else if file_type.is_dir() { + "S_IFDIR" + } else if file_type.is_symlink() { + "S_IFLNK" + } else { + // Certain file types are only available when the host is a Unix system. + #[cfg(unix)] + { + if file_type.is_socket() { + return "S_IFSOCK"; + } else if file_type.is_fifo() { + return "S_IFIFO"; + } else if file_type.is_char_device() { + return "S_IFCHR"; + } else if file_type.is_block_device() { + return "S_IFBLK"; + } + } + "S_IFREG" + } +} + /// Stores a file's metadata in order to avoid code duplication in the different metadata related /// shims. -pub struct FileMetadata { +struct FileMetadata { mode: Scalar, size: u64, created: Option<(u64, u32)>, @@ -1694,18 +1694,20 @@ fn from_fd_num<'tcx>( let Some(fd) = ecx.machine.fds.get(fd_num) else { return interp_ok(Err(LibcError("EBADF"))); }; - fd.fstat(ecx) + match fd.metadata()? { + Either::Left(host) => Self::from_meta(ecx, host), + Either::Right(name) => Self::synthetic(ecx, name), + } } - pub(crate) fn synthetic<'tcx>( + fn synthetic<'tcx>( ecx: &mut MiriInterpCx<'tcx>, mode_name: &str, - size: u64, ) -> InterpResult<'tcx, Result> { let mode = ecx.eval_libc(mode_name); interp_ok(Ok(FileMetadata { mode, - size, + size: 0, created: None, accessed: None, modified: None, @@ -1715,7 +1717,7 @@ pub(crate) fn synthetic<'tcx>( })) } - pub(crate) fn from_meta<'tcx>( + fn from_meta<'tcx>( ecx: &mut MiriInterpCx<'tcx>, metadata: Result, ) -> InterpResult<'tcx, Result> { diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs index 48085fa6ae26..bd07e13d47fb 100644 --- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs +++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs @@ -10,7 +10,7 @@ use crate::shims::files::{ DynFileDescriptionRef, FdId, FdNum, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; -use crate::shims::unix::{FileMetadata, UnixFileDescription}; +use crate::shims::unix::UnixFileDescription; use crate::*; type EpollEventKey = (FdId, FdNum); @@ -119,12 +119,11 @@ fn name(&self) -> &'static str { "epoll" } - fn fstat<'tcx>( + fn metadata<'tcx>( &self, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { + ) -> InterpResult<'tcx, Either, &'static str>> { // On Linux, epoll is an "anonymous inode" reported as S_IFREG. - FileMetadata::synthetic(ecx, "S_IFREG", 0) + interp_ok(Either::Right("S_IFREG")) } fn destroy<'tcx>( diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs index 03bac1e7270d..7cccbd0e275c 100644 --- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs +++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs @@ -5,8 +5,8 @@ use crate::concurrency::VClock; use crate::shims::files::{FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef}; +use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; -use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// Maximum value that the eventfd counter can hold. @@ -37,12 +37,11 @@ fn name(&self) -> &'static str { "event" } - fn fstat<'tcx>( + fn metadata<'tcx>( &self, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { + ) -> InterpResult<'tcx, Either, &'static str>> { // On Linux, eventfd is an "anonymous inode" reported as S_IFREG. - FileMetadata::synthetic(ecx, "S_IFREG", 0) + interp_ok(Either::Right("S_IFREG")) } fn destroy<'tcx>( diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 9e8fa2a12d49..c55a28bfa7b2 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -19,7 +19,7 @@ // All the Unix-specific extension traits pub use self::env::{EvalContextExt as _, UnixEnvVars}; pub use self::fd::{EvalContextExt as _, UnixFileDescription}; -pub use self::fs::{DirTable, EvalContextExt as _, FileMetadata}; +pub use self::fs::{DirTable, EvalContextExt as _}; pub use self::linux_like::epoll::EpollInterestTable; pub use self::mem::EvalContextExt as _; pub use self::socket::EvalContextExt as _; diff --git a/src/tools/miri/src/shims/unix/virtual_socket.rs b/src/tools/miri/src/shims/unix/virtual_socket.rs index 16eba61c56ba..51bd30840ffb 100644 --- a/src/tools/miri/src/shims/unix/virtual_socket.rs +++ b/src/tools/miri/src/shims/unix/virtual_socket.rs @@ -12,8 +12,8 @@ use crate::shims::files::{ EvalContextExt as _, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef, }; +use crate::shims::unix::UnixFileDescription; use crate::shims::unix::linux_like::epoll::{EpollEvents, EvalContextExt as _}; -use crate::shims::unix::{FileMetadata, UnixFileDescription}; use crate::*; /// The maximum capacity of the socketpair buffer in bytes. @@ -83,15 +83,14 @@ fn name(&self) -> &'static str { } } - fn fstat<'tcx>( + fn metadata<'tcx>( &self, - ecx: &mut MiriInterpCx<'tcx>, - ) -> InterpResult<'tcx, Result> { + ) -> InterpResult<'tcx, Either, &'static str>> { let mode_name = match self.fd_type { VirtualSocketType::Socketpair => "S_IFSOCK", VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO", }; - FileMetadata::synthetic(ecx, mode_name, 0) + interp_ok(Either::Right(mode_name)) } fn destroy<'tcx>( diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs index ddbb2b1de018..a6efc36d75f6 100644 --- a/src/tools/miri/src/shims/windows/fs.rs +++ b/src/tools/miri/src/shims/windows/fs.rs @@ -22,8 +22,10 @@ fn name(&self) -> &'static str { "directory" } - fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(self.path.metadata()) + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(self.path.metadata())) } fn destroy<'tcx>( @@ -49,8 +51,10 @@ fn name(&self) -> &'static str { "metadata-only" } - fn host_metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result> { - interp_ok(Ok(self.meta.clone())) + fn metadata<'tcx>( + &self, + ) -> InterpResult<'tcx, Either, &'static str>> { + interp_ok(Either::Left(Ok(self.meta.clone()))) } fn destroy<'tcx>( @@ -328,12 +332,16 @@ fn GetFileInformationByHandle( this.invalid_handle("GetFileInformationByHandle")? }; - let metadata = match desc.host_metadata()? { - Ok(meta) => meta, - Err(e) => { + let metadata = match desc.metadata()? { + Either::Left(Ok(meta)) => meta, + Either::Left(Err(e)) => { this.set_last_error(e)?; return interp_ok(this.eval_windows("c", "FALSE")); } + Either::Right(_mode) => + throw_unsup_format!( + "`GetFileInformationByHandle` is not supported on non-file-backed handles" + ), }; let size = metadata.len(); From f107bb85a21bbf5824854fbf0f2546505f4732a0 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 26 Apr 2026 21:33:38 +1000 Subject: [PATCH 27/30] Regression test for improper spans in inclusive-range suggestions --- tests/ui/parser/range-inclusive-suggestion-span.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/ui/parser/range-inclusive-suggestion-span.rs diff --git a/tests/ui/parser/range-inclusive-suggestion-span.rs b/tests/ui/parser/range-inclusive-suggestion-span.rs new file mode 100644 index 000000000000..eed115d0ac89 --- /dev/null +++ b/tests/ui/parser/range-inclusive-suggestion-span.rs @@ -0,0 +1,14 @@ +#![crate_type = "rlib"] + +// Suggestions for range patterns should not perform span manipulations that +// assume the range token is ASCII, because it could have been recovered from +// similar-looking Unicode characters. +// +// Regression test for . + +// FIXME: The ICE is fixed in a subsequent commit. +//@ known-bug: #155799 +//@ failure-status: 101 + +// These dots are U+00B7 MIDDLE DOT, not an ASCII period. +fn dot_dot_dot() { ··· } From 9ceed255b5c4356ab6093e98ba3e4fe2a405f98b Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 26 Apr 2026 21:02:20 +1000 Subject: [PATCH 28/30] Avoid improper spans when `...` or `..=` is recovered from non-ASCII This avoids an ICE due to indexing into the middle of a multi-byte character. --- compiler/rustc_parse/src/errors.rs | 10 +- compiler/rustc_parse/src/parser/pat.rs | 12 +- .../parser/range-inclusive-suggestion-span.rs | 40 ++- .../range-inclusive-suggestion-span.stderr | 334 ++++++++++++++++++ 4 files changed, 378 insertions(+), 18 deletions(-) create mode 100644 tests/ui/parser/range-inclusive-suggestion-span.stderr diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index cc1e0ff85dae..1829592d6d16 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -1140,14 +1140,13 @@ pub(crate) struct InclusiveRangeMatchArrow { #[primary_span] pub arrow: Span, #[label("this is parsed as an inclusive range `..=`")] - pub span: Span, #[suggestion( "add a space between the pattern and `=>`", style = "verbose", - code = " ", + code = ".. =", applicability = "machine-applicable" )] - pub after_pat: Span, + pub span: Span, } #[derive(Diagnostic)] @@ -1155,14 +1154,13 @@ pub(crate) struct InclusiveRangeMatchArrow { #[note("inclusive ranges must be bounded at the end (`..=b` or `a..=b`)")] pub(crate) struct InclusiveRangeNoEnd { #[primary_span] - pub span: Span, #[suggestion( "use `..` instead", - code = "", + code = "..", applicability = "machine-applicable", style = "verbose" )] - pub suggestion: Span, + pub span: Span, } #[derive(Subdiagnostic)] diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index b5c33d740872..f36127ec8f0a 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1225,7 +1225,7 @@ fn parse_pat_range_begin_with( pub(super) fn inclusive_range_with_incorrect_end(&mut self) -> ErrorGuaranteed { let tok = &self.token; let span = self.prev_token.span; - // If the user typed "..==" instead of "..=", we want to give them + // If the user typed "..==" or "...=" instead of "..=", we want to give them // a specific error message telling them to use "..=". // If they typed "..=>", suggest they use ".. =>". // Otherwise, we assume that they meant to type a half open exclusive @@ -1243,14 +1243,10 @@ pub(super) fn inclusive_range_with_incorrect_end(&mut self) -> ErrorGuaranteed { self.dcx().emit_err(InclusiveRangeExtraEquals { span: span_with_eq }) } - token::Gt if no_space => { - let after_pat = span.with_hi(span.hi() - BytePos(1)).shrink_to_hi(); - self.dcx().emit_err(InclusiveRangeMatchArrow { span, arrow: tok.span, after_pat }) + token::Gt if self.prev_token.kind == token::DotDotEq && no_space => { + self.dcx().emit_err(InclusiveRangeMatchArrow { span, arrow: tok.span }) } - _ => self.dcx().emit_err(InclusiveRangeNoEnd { - span, - suggestion: span.with_lo(span.hi() - BytePos(1)), - }), + _ => self.dcx().emit_err(InclusiveRangeNoEnd { span }), } } diff --git a/tests/ui/parser/range-inclusive-suggestion-span.rs b/tests/ui/parser/range-inclusive-suggestion-span.rs index eed115d0ac89..06d1e42a1375 100644 --- a/tests/ui/parser/range-inclusive-suggestion-span.rs +++ b/tests/ui/parser/range-inclusive-suggestion-span.rs @@ -6,9 +6,41 @@ // // Regression test for . -// FIXME: The ICE is fixed in a subsequent commit. -//@ known-bug: #155799 -//@ failure-status: 101 - // These dots are U+00B7 MIDDLE DOT, not an ASCII period. fn dot_dot_dot() { ··· } +//~^ ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR unexpected token: `...` +//~| ERROR inclusive range with no end + +fn dot_dot_dot_eq() { ···= } +//~^ ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR unexpected token: `...` +//~| ERROR unexpected `=` after inclusive range + +fn dot_dot_dot_gt() { ···> } +//~^ ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR unexpected token: `...` +//~| ERROR inclusive range with no end +//~| ERROR expected one of `;` or `}`, found `>` + +fn dot_dot_eq() { ··= } +//~^ ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR inclusive range with no end + +fn dot_dot_eq_eq() { ··== } +//~^ ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR unexpected `=` after inclusive range + +fn dot_dot_eq_gt() { ··=> } +//~^ ERROR unknown start of token +//~| ERROR unknown start of token +//~| ERROR unexpected `>` after inclusive range +//~| ERROR expected one of `;` or `}`, found `>` diff --git a/tests/ui/parser/range-inclusive-suggestion-span.stderr b/tests/ui/parser/range-inclusive-suggestion-span.stderr new file mode 100644 index 000000000000..374328858935 --- /dev/null +++ b/tests/ui/parser/range-inclusive-suggestion-span.stderr @@ -0,0 +1,334 @@ +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:10:20 + | +LL | fn dot_dot_dot() { ··· } + | ^^^ + | + = note: character appears 2 more times +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot() { ··· } +LL + fn dot_dot_dot() { ... } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:10:21 + | +LL | fn dot_dot_dot() { ··· } + | ^^ + | + = note: character appears once more +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot() { ··· } +LL + fn dot_dot_dot() { ·.. } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:10:22 + | +LL | fn dot_dot_dot() { ··· } + | ^ + | +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot() { ··· } +LL + fn dot_dot_dot() { ··. } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:17:23 + | +LL | fn dot_dot_dot_eq() { ···= } + | ^^^ + | + = note: character appears 2 more times +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot_eq() { ···= } +LL + fn dot_dot_dot_eq() { ...= } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:17:24 + | +LL | fn dot_dot_dot_eq() { ···= } + | ^^ + | + = note: character appears once more +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot_eq() { ···= } +LL + fn dot_dot_dot_eq() { ·..= } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:17:25 + | +LL | fn dot_dot_dot_eq() { ···= } + | ^ + | +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot_eq() { ···= } +LL + fn dot_dot_dot_eq() { ··.= } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:24:23 + | +LL | fn dot_dot_dot_gt() { ···> } + | ^^^ + | + = note: character appears 2 more times +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot_gt() { ···> } +LL + fn dot_dot_dot_gt() { ...> } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:24:24 + | +LL | fn dot_dot_dot_gt() { ···> } + | ^^ + | + = note: character appears once more +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot_gt() { ···> } +LL + fn dot_dot_dot_gt() { ·..> } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:24:25 + | +LL | fn dot_dot_dot_gt() { ···> } + | ^ + | +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_dot_gt() { ···> } +LL + fn dot_dot_dot_gt() { ··.> } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:32:19 + | +LL | fn dot_dot_eq() { ··= } + | ^^ + | + = note: character appears once more +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_eq() { ··= } +LL + fn dot_dot_eq() { ..= } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:32:20 + | +LL | fn dot_dot_eq() { ··= } + | ^ + | +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_eq() { ··= } +LL + fn dot_dot_eq() { ·.= } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:37:22 + | +LL | fn dot_dot_eq_eq() { ··== } + | ^^ + | + = note: character appears once more +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_eq_eq() { ··== } +LL + fn dot_dot_eq_eq() { ..== } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:37:23 + | +LL | fn dot_dot_eq_eq() { ··== } + | ^ + | +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_eq_eq() { ··== } +LL + fn dot_dot_eq_eq() { ·.== } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:42:22 + | +LL | fn dot_dot_eq_gt() { ··=> } + | ^^ + | + = note: character appears once more +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_eq_gt() { ··=> } +LL + fn dot_dot_eq_gt() { ..=> } + | + +error: unknown start of token: \u{b7} + --> $DIR/range-inclusive-suggestion-span.rs:42:23 + | +LL | fn dot_dot_eq_gt() { ··=> } + | ^ + | +help: Unicode character '·' (Middle Dot) looks like '.' (Period), but it is not + | +LL - fn dot_dot_eq_gt() { ··=> } +LL + fn dot_dot_eq_gt() { ·.=> } + | + +error: unexpected token: `...` + --> $DIR/range-inclusive-suggestion-span.rs:10:20 + | +LL | fn dot_dot_dot() { ··· } + | ^^^ + | +help: use `..` for an exclusive range + | +LL - fn dot_dot_dot() { ··· } +LL + fn dot_dot_dot() { .. } + | +help: or `..=` for an inclusive range + | +LL - fn dot_dot_dot() { ··· } +LL + fn dot_dot_dot() { ..= } + | + +error[E0586]: inclusive range with no end + --> $DIR/range-inclusive-suggestion-span.rs:10:20 + | +LL | fn dot_dot_dot() { ··· } + | ^^^ + | + = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`) +help: use `..` instead + | +LL - fn dot_dot_dot() { ··· } +LL + fn dot_dot_dot() { .. } + | + +error: unexpected token: `...` + --> $DIR/range-inclusive-suggestion-span.rs:17:23 + | +LL | fn dot_dot_dot_eq() { ···= } + | ^^^ + | +help: use `..` for an exclusive range + | +LL - fn dot_dot_dot_eq() { ···= } +LL + fn dot_dot_dot_eq() { ..= } + | +help: or `..=` for an inclusive range + | +LL - fn dot_dot_dot_eq() { ···= } +LL + fn dot_dot_dot_eq() { ..== } + | + +error: unexpected `=` after inclusive range + --> $DIR/range-inclusive-suggestion-span.rs:17:23 + | +LL | fn dot_dot_dot_eq() { ···= } + | ^^^^ + | + = note: inclusive ranges end with a single equals sign (`..=`) +help: use `..=` instead + | +LL - fn dot_dot_dot_eq() { ···= } +LL + fn dot_dot_dot_eq() { ..= } + | + +error: unexpected token: `...` + --> $DIR/range-inclusive-suggestion-span.rs:24:23 + | +LL | fn dot_dot_dot_gt() { ···> } + | ^^^ + | +help: use `..` for an exclusive range + | +LL - fn dot_dot_dot_gt() { ···> } +LL + fn dot_dot_dot_gt() { ..> } + | +help: or `..=` for an inclusive range + | +LL - fn dot_dot_dot_gt() { ···> } +LL + fn dot_dot_dot_gt() { ..=> } + | + +error[E0586]: inclusive range with no end + --> $DIR/range-inclusive-suggestion-span.rs:24:23 + | +LL | fn dot_dot_dot_gt() { ···> } + | ^^^ + | + = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`) +help: use `..` instead + | +LL - fn dot_dot_dot_gt() { ···> } +LL + fn dot_dot_dot_gt() { ..> } + | + +error: expected one of `;` or `}`, found `>` + --> $DIR/range-inclusive-suggestion-span.rs:24:26 + | +LL | fn dot_dot_dot_gt() { ···> } + | ^ expected one of `;` or `}` + +error[E0586]: inclusive range with no end + --> $DIR/range-inclusive-suggestion-span.rs:32:19 + | +LL | fn dot_dot_eq() { ··= } + | ^^^ + | + = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`) +help: use `..` instead + | +LL - fn dot_dot_eq() { ··= } +LL + fn dot_dot_eq() { .. } + | + +error: unexpected `=` after inclusive range + --> $DIR/range-inclusive-suggestion-span.rs:37:22 + | +LL | fn dot_dot_eq_eq() { ··== } + | ^^^^ + | + = note: inclusive ranges end with a single equals sign (`..=`) +help: use `..=` instead + | +LL - fn dot_dot_eq_eq() { ··== } +LL + fn dot_dot_eq_eq() { ..= } + | + +error: unexpected `>` after inclusive range + --> $DIR/range-inclusive-suggestion-span.rs:42:25 + | +LL | fn dot_dot_eq_gt() { ··=> } + | ---^ + | | + | this is parsed as an inclusive range `..=` + | +help: add a space between the pattern and `=>` + | +LL - fn dot_dot_eq_gt() { ··=> } +LL + fn dot_dot_eq_gt() { .. => } + | + +error: expected one of `;` or `}`, found `>` + --> $DIR/range-inclusive-suggestion-span.rs:42:25 + | +LL | fn dot_dot_eq_gt() { ··=> } + | ^ expected one of `;` or `}` + +error: aborting due to 26 previous errors + +For more information about this error, try `rustc --explain E0586`. From 48fe89f9941ba0b79618e44e0176909ad85decb7 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:23:53 +0200 Subject: [PATCH 29/30] add default field values to diagnostic FormatArgs --- .../src/diagnostics/conflict_errors.rs | 9 +-------- compiler/rustc_expand/src/lib.rs | 1 + compiler/rustc_expand/src/mbe/diagnostics.rs | 13 +------------ compiler/rustc_hir/src/attrs/diagnostic.rs | 7 ++++--- compiler/rustc_hir/src/lib.rs | 1 + compiler/rustc_resolve/src/imports.rs | 7 +------ 6 files changed, 9 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 2735c800c4a3..f7d35f3ff3b4 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -156,14 +156,7 @@ pub(crate) fn report_use_of_moved_or_uninitialized( .collect(); generic_args.push((kw::SelfUpper, this.clone())); - let args = FormatArgs { - this, - // Unused - this_sugared: String::new(), - // Unused - item_context: "", - generic_args, - }; + let args = FormatArgs { this, generic_args, .. }; let CustomDiagnostic { message, label, notes, parent_label: _ } = directive.eval(None, &args); diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index d2ac7103bccb..5068501a0e2d 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -1,6 +1,7 @@ // tidy-alphabetical-start #![allow(internal_features)] #![feature(associated_type_defaults)] +#![feature(default_field_values)] #![feature(macro_metavar_expr)] #![feature(proc_macro_diagnostic)] #![feature(proc_macro_internals)] diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index 4e7e51c0a43c..16b21b062cce 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -77,19 +77,8 @@ pub(super) fn failed_to_match_macro( let CustomDiagnostic { message: custom_message, label: custom_label, notes: custom_notes, .. } = { - let macro_name = name.to_string(); on_unmatch_args - .map(|directive| { - directive.eval( - None, - &FormatArgs { - this: macro_name.clone(), - this_sugared: macro_name, - item_context: "macro invocation", - generic_args: Vec::new(), - }, - ) - }) + .map(|directive| directive.eval(None, &FormatArgs { this: name.to_string(), .. })) .unwrap_or_default() }; diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 66cdf2be8fc4..2beafee54541 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -218,10 +218,11 @@ fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { /// ``` #[derive(Debug)] pub struct FormatArgs { + /// The name of the item the attribute is on. pub this: String, - pub this_sugared: String, - pub item_context: &'static str, - pub generic_args: Vec<(Symbol, String)>, + pub this_sugared: String = String::new(), + pub item_context: &'static str = "", + pub generic_args: Vec<(Symbol, String)> = Vec::new(), } #[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs index c2d9f879cd60..7c2bf3c5b279 100644 --- a/compiler/rustc_hir/src/lib.rs +++ b/compiler/rustc_hir/src/lib.rs @@ -7,6 +7,7 @@ #![feature(closure_track_caller)] #![feature(const_default)] #![feature(const_trait_impl)] +#![feature(default_field_values)] #![feature(derive_const)] #![feature(exhaustive_patterns)] #![feature(never_type)] diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 198ec4080816..b6ba35f0f3db 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -886,12 +886,7 @@ fn throw_unresolved_import_error( let args = FormatArgs { this, - // Unused - this_sugared: String::new(), - // Unused - item_context: "", - // Unused - generic_args: Vec::new(), + .. }; let CustomDiagnostic { message, label, notes, .. } = directive.eval(None, &args); From 28c079ae40dbac7ab4a88ef7fe766bd9339e6857 Mon Sep 17 00:00:00 2001 From: Qai Juang <237468078+qaijuang@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:30:46 +0000 Subject: [PATCH 30/30] Suggest `.iter()` for shared projections * Suggest `.iter()` for shared projections * address few nits * a few improvements --- .../rustc_hir_typeck/src/expr_use_visitor.rs | 26 ++++++- .../rustc_hir_typeck/src/method/suggest.rs | 73 ++++++++++++++++++- .../collect-without-into-iter-call.rs | 38 +++++++++- .../collect-without-into-iter-call.stderr | 50 ++++++++++++- 4 files changed, 178 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index f3d0b4d000c2..a6129d97a328 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -2,7 +2,7 @@ //! normal visitor, which just walks the entire body in one shot, the //! `ExprUseVisitor` determines how expressions are being used. //! -//! In the compiler, this is only used for upvar inference, but there +//! In the compiler, this is only used for upvar inference and diagnostics, but there //! are many uses within clippy. use std::cell::{Ref, RefCell}; @@ -1855,3 +1855,27 @@ fn is_multivariant_adt(&self, ty: Ty<'tcx>, span: Span) -> bool { } } } + +struct ExprPlaceDelegate; + +impl<'tcx> Delegate<'tcx> for ExprPlaceDelegate { + fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} + + fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} + +/// Categorizes `expr` as a place for diagnostic suggestions. +/// +/// This should be used for diagnostics purpose only. +pub(crate) fn expr_place<'tcx>( + fcx: &FnCtxt<'_, 'tcx>, + expr: &hir::Expr<'_>, +) -> Result, ErrorGuaranteed> { + ExprUseVisitor::new(fcx, ExprPlaceDelegate).cat_expr(expr) +} diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index c0613eef52c3..7cf4822e4df6 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -48,6 +48,7 @@ use super::probe::{AutorefOrPtrAdjustment, IsSuggestion, Mode, ProbeScope}; use super::{CandidateSource, MethodError, NoMatchData}; use crate::errors::{self, CandidateTraitNote, NoAssociatedItem}; +use crate::expr_use_visitor::expr_place; use crate::method::probe::UnsatisfiedPredicates; use crate::{Expectation, FnCtxt}; @@ -189,6 +190,70 @@ fn predicate_bounds_generic_param<'tcx>( false } + // Pick the iterator method to suggest: `.into_iter()` by default, and + // `.iter()`/`.iter_mut()` for projections through references. + fn preferred_iterator_method( + &self, + source: SelfSource<'tcx>, + rcvr_ty: Ty<'tcx>, + ) -> Option { + let SelfSource::MethodCall(rcvr_expr) = source else { + return Some(sym::into_iter); + }; + + let rcvr_expr = rcvr_expr.peel_drop_temps().peel_blocks(); + let Ok(place_with_id) = expr_place(self, rcvr_expr) else { + return None; + }; + + let mut projection_mutability = None; + for pointer_ty in place_with_id.place.deref_tys() { + match self.structurally_resolve_type(rcvr_expr.span, pointer_ty).kind() { + ty::Ref(.., Mutability::Not) => { + projection_mutability = Some(Mutability::Not); + break; + } + ty::Ref(.., Mutability::Mut) => { + projection_mutability.get_or_insert(Mutability::Mut); + } + ty::RawPtr(..) => return None, + _ => {} + } + } + + // Keep `.into_iter()` for receivers like `&Vec<_>`; only projections that + // dereference a reference need to switch to `iter`/`iter_mut`. + let Some(projection_mutability) = projection_mutability else { + return Some(sym::into_iter); + }; + + let call_expr = self.tcx.hir_expect_expr(self.tcx.parent_hir_id(rcvr_expr.hir_id)); + // `IntoIterator` does not imply inherent `iter`/`iter_mut` methods. + let has_method = |method_name| { + self.lookup_probe_for_diagnostic( + Ident::with_dummy_span(method_name), + rcvr_ty, + call_expr, + ProbeScope::TraitsInScope, + None, + ) + .is_ok() + }; + + match projection_mutability { + Mutability::Not => has_method(sym::iter).then_some(sym::iter), + Mutability::Mut => { + if has_method(sym::iter_mut) { + Some(sym::iter_mut) + } else if has_method(sym::iter) { + Some(sym::iter) + } else { + None + } + } + } + } + #[instrument(level = "debug", skip(self))] pub(crate) fn report_method_error( &self, @@ -855,10 +920,12 @@ fn suggest_unsatisfied_ty_or_trait( } else if self.impl_into_iterator_should_be_iterator(rcvr_ty, span, unsatisfied_predicates) { err.span_label(span, format!("`{rcvr_ty}` is not an iterator")); - if !span.in_external_macro(self.tcx.sess.source_map()) { + if !span.in_external_macro(self.tcx.sess.source_map()) + && let Some(method_name) = self.preferred_iterator_method(source, rcvr_ty) + { err.multipart_suggestion( - "call `.into_iter()` first", - vec![(span.shrink_to_lo(), format!("into_iter()."))], + format!("call `.{method_name}()` first"), + vec![(span.shrink_to_lo(), format!("{method_name}()."))], Applicability::MaybeIncorrect, ); } diff --git a/tests/ui/did_you_mean/collect-without-into-iter-call.rs b/tests/ui/did_you_mean/collect-without-into-iter-call.rs index ee4d75615bd0..ca4b104e6347 100644 --- a/tests/ui/did_you_mean/collect-without-into-iter-call.rs +++ b/tests/ui/did_you_mean/collect-without-into-iter-call.rs @@ -1,12 +1,14 @@ -// Tests that the compiler suggests an `into_iter` call when an `Iterator` method -// is called on something that implements `IntoIterator` +// Tests that the compiler suggests an iterator method when an `Iterator` method +// is called on something that implements `IntoIterator`. fn main() { let items = items(); let other_items = items.map(|i| i + 1); //~^ ERROR no method named `map` found for opaque type `impl IntoIterator` in the current scope + //~| HELP: call `.into_iter()` first let vec: Vec = items.collect(); //~^ ERROR no method named `collect` found for opaque type `impl IntoIterator` in the current scope + //~| HELP: call `.into_iter()` first } fn items() -> impl IntoIterator { @@ -16,4 +18,36 @@ fn items() -> impl IntoIterator { fn process(items: impl IntoIterator) -> Vec { items.collect() //~^ ERROR no method named `collect` found for type parameter `impl IntoIterator` in the current scope + //~| HELP: call `.into_iter()` first +} + +// Regression test for https://github.com/rust-lang/rust/issues/155365 +struct Demo { + contents: Vec, +} + +impl Demo { + fn count_odds(&self) -> usize { + self.contents.filter(|v| *v % 2 == 1).count() + //~^ ERROR no method named `filter` found for struct `Vec` in the current scope + //~| HELP: call `.iter()` first + } + + fn increment(&mut self) { + self.contents.for_each(|v| *v += 1) + //~^ ERROR no method named `for_each` found for struct `Vec` in the current scope + //~| HELP: call `.iter_mut()` first + } +} + +fn count_odds_param(contents: &Vec) -> usize { + contents.filter(|v| *v % 2 == 1).count() + //~^ ERROR no method named `filter` found for reference `&Vec` in the current scope + //~| HELP: call `.into_iter()` first +} + +fn count_odds_explicit_deref(contents: &Vec) -> usize { + (*contents).filter(|v| *v % 2 == 1).count() + //~^ ERROR no method named `filter` found for struct `Vec` in the current scope + //~| HELP: call `.iter()` first } diff --git a/tests/ui/did_you_mean/collect-without-into-iter-call.stderr b/tests/ui/did_you_mean/collect-without-into-iter-call.stderr index 797bd1e9e6f1..f3acaa532267 100644 --- a/tests/ui/did_you_mean/collect-without-into-iter-call.stderr +++ b/tests/ui/did_you_mean/collect-without-into-iter-call.stderr @@ -10,7 +10,7 @@ LL | let other_items = items.into_iter().map(|i| i + 1); | ++++++++++++ error[E0599]: no method named `collect` found for opaque type `impl IntoIterator` in the current scope - --> $DIR/collect-without-into-iter-call.rs:8:31 + --> $DIR/collect-without-into-iter-call.rs:9:31 | LL | let vec: Vec = items.collect(); | ^^^^^^^ `impl IntoIterator` is not an iterator @@ -21,7 +21,7 @@ LL | let vec: Vec = items.into_iter().collect(); | ++++++++++++ error[E0599]: no method named `collect` found for type parameter `impl IntoIterator` in the current scope - --> $DIR/collect-without-into-iter-call.rs:17:11 + --> $DIR/collect-without-into-iter-call.rs:19:11 | LL | items.collect() | ^^^^^^^ `impl IntoIterator` is not an iterator @@ -31,6 +31,50 @@ help: call `.into_iter()` first LL | items.into_iter().collect() | ++++++++++++ -error: aborting due to 3 previous errors +error[E0599]: no method named `filter` found for struct `Vec` in the current scope + --> $DIR/collect-without-into-iter-call.rs:31:23 + | +LL | self.contents.filter(|v| *v % 2 == 1).count() + | ^^^^^^ `Vec` is not an iterator + | +help: call `.iter()` first + | +LL | self.contents.iter().filter(|v| *v % 2 == 1).count() + | +++++++ + +error[E0599]: no method named `for_each` found for struct `Vec` in the current scope + --> $DIR/collect-without-into-iter-call.rs:37:23 + | +LL | self.contents.for_each(|v| *v += 1) + | ^^^^^^^^ `Vec` is not an iterator + | +help: call `.iter_mut()` first + | +LL | self.contents.iter_mut().for_each(|v| *v += 1) + | +++++++++++ + +error[E0599]: no method named `filter` found for reference `&Vec` in the current scope + --> $DIR/collect-without-into-iter-call.rs:44:14 + | +LL | contents.filter(|v| *v % 2 == 1).count() + | ^^^^^^ `&Vec` is not an iterator + | +help: call `.into_iter()` first + | +LL | contents.into_iter().filter(|v| *v % 2 == 1).count() + | ++++++++++++ + +error[E0599]: no method named `filter` found for struct `Vec` in the current scope + --> $DIR/collect-without-into-iter-call.rs:50:17 + | +LL | (*contents).filter(|v| *v % 2 == 1).count() + | ^^^^^^ `Vec` is not an iterator + | +help: call `.iter()` first + | +LL | (*contents).iter().filter(|v| *v % 2 == 1).count() + | +++++++ + +error: aborting due to 7 previous errors For more information about this error, try `rustc --explain E0599`.