From 842c087427b1bd6d70da9ba3b1e7a7e013b786b0 Mon Sep 17 00:00:00 2001 From: Flakebi Date: Mon, 29 Dec 2025 21:55:30 +0100 Subject: [PATCH 01/22] Fix requires_lto targets needing lto set in cargo Targets that set `requires_lto = true` were not actually using lto when compiling with cargo by default. They needed an extra `lto = true` in `Cargo.toml` to work. Fix this by letting lto take precedence over the `embed_bitcode` flag when lto is required by a target. If both these flags would be supplied by the user, an error is generated. However, this did not happen when lto was requested by the target instead of the user. --- compiler/rustc_codegen_ssa/src/back/write.rs | 2 +- .../src/platform-support/amdgcn-amd-amdhsa.md | 5 ---- tests/run-make-cargo/amdgpu-lto/Cargo.toml | 8 ++++++ tests/run-make-cargo/amdgpu-lto/lib.rs | 15 ++++++++++ tests/run-make-cargo/amdgpu-lto/rmake.rs | 28 +++++++++++++++++++ 5 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 tests/run-make-cargo/amdgpu-lto/Cargo.toml create mode 100644 tests/run-make-cargo/amdgpu-lto/lib.rs create mode 100644 tests/run-make-cargo/amdgpu-lto/rmake.rs diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 3e36bd8552b18..8409c927d1564 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -156,7 +156,7 @@ impl ModuleConfig { // `#![no_builtins]` is assumed to not participate in LTO and // instead goes on to generate object code. EmitObj::Bitcode - } else if need_bitcode_in_object(tcx) { + } else if need_bitcode_in_object(tcx) || sess.target.requires_lto { EmitObj::ObjectCode(BitcodeSection::Full) } else { EmitObj::ObjectCode(BitcodeSection::None) diff --git a/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md b/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md index dbdb96283a5fa..8934e7085b8d7 100644 --- a/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md +++ b/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md @@ -59,11 +59,6 @@ Build the library as `cdylib`: # Cargo.toml [lib] crate-type = ["cdylib"] - -[profile.dev] -lto = true # LTO must be explicitly enabled for now -[profile.release] -lto = true ``` The target-cpu must be from the list [supported by LLVM] (or printed with `rustc --target amdgcn-amd-amdhsa --print target-cpus`). diff --git a/tests/run-make-cargo/amdgpu-lto/Cargo.toml b/tests/run-make-cargo/amdgpu-lto/Cargo.toml new file mode 100644 index 0000000000000..b2607b747fd7f --- /dev/null +++ b/tests/run-make-cargo/amdgpu-lto/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "amdgpu_lto" +version = "0.1.0" +edition = "2024" + +[lib] +path = "lib.rs" +crate-type = ["cdylib"] diff --git a/tests/run-make-cargo/amdgpu-lto/lib.rs b/tests/run-make-cargo/amdgpu-lto/lib.rs new file mode 100644 index 0000000000000..d17cf5a8316ca --- /dev/null +++ b/tests/run-make-cargo/amdgpu-lto/lib.rs @@ -0,0 +1,15 @@ +#![feature(abi_gpu_kernel)] +#![no_std] + +#[panic_handler] +fn panic_handler(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[unsafe(no_mangle)] +fn foo(a: i32, b: i32) -> i32 { + a + b +} + +#[unsafe(no_mangle)] +extern "gpu-kernel" fn kernel() {} diff --git a/tests/run-make-cargo/amdgpu-lto/rmake.rs b/tests/run-make-cargo/amdgpu-lto/rmake.rs new file mode 100644 index 0000000000000..cb3dc81f34d15 --- /dev/null +++ b/tests/run-make-cargo/amdgpu-lto/rmake.rs @@ -0,0 +1,28 @@ +// Check that compiling for the amdgpu target which needs LTO works with a default +// cargo configuration. + +//@ needs-llvm-components: amdgpu +//@ needs-rust-lld + +#![deny(warnings)] + +use run_make_support::{cargo, path}; + +fn main() { + let target_dir = path("target"); + + cargo() + .args(&[ + "build", + "--release", + "--lib", + "--manifest-path", + "Cargo.toml", + "-Zbuild-std=core", + "--target", + "amdgcn-amd-amdhsa", + ]) + .env("RUSTFLAGS", "-Ctarget-cpu=gfx900") + .env("CARGO_TARGET_DIR", &target_dir) + .run(); +} From 47bec22de111ca5d3e2805b1f0f94aa739af904d Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Mon, 12 Jan 2026 22:52:15 +0530 Subject: [PATCH 02/22] std: sys: process: uefi: Add program searching - Follow UEFI Shell search flow to search for programs while launching. - Tested using OVMF on QEMU. Signed-off-by: Ayush Singh --- library/std/src/sys/process/uefi.rs | 36 +++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/process/uefi.rs b/library/std/src/sys/process/uefi.rs index 31914aeb67c59..6015edea32a9d 100644 --- a/library/std/src/sys/process/uefi.rs +++ b/library/std/src/sys/process/uefi.rs @@ -5,7 +5,7 @@ use crate::collections::BTreeMap; pub use crate::ffi::OsString as EnvKey; use crate::ffi::{OsStr, OsString}; use crate::num::{NonZero, NonZeroI32}; -use crate::path::Path; +use crate::path::{Path, PathBuf}; use crate::process::StdioPipes; use crate::sys::fs::File; use crate::sys::io::error_string; @@ -138,7 +138,9 @@ impl Command { } pub fn output(command: &mut Command) -> io::Result<(ExitStatus, Vec, Vec)> { - let mut cmd = uefi_command_internal::Image::load_image(&command.prog)?; + let prog_path = resolve_program(&command.prog) + .ok_or(io::const_error!(io::ErrorKind::NotFound, "could not find the program."))?; + let mut cmd = uefi_command_internal::Image::load_image(prog_path.as_os_str())?; // UEFI adds the bin name by default if !command.args.is_empty() { @@ -366,6 +368,36 @@ pub fn read_output( match out.diverge() {} } +// Search for programs similar to UEFI Shell defined in Section 3.6.1. It follows the following flow: +// 1. If program is already absolute path, just check if it exists. +// 2. For non-absolute path, search relative to current directory. +// 3. Search the path list sequentially. +// +// [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_2_2.pdf). +fn resolve_program + ?Sized>(prog: &S) -> Option { + let absolute_prog_path = crate::path::absolute(prog.as_ref()).ok()?; + + match crate::fs::exists(&absolute_prog_path) { + Ok(true) => return Some(absolute_prog_path), + // If program path was already absolute and is not found, then stop. + Ok(false) if Path::new(prog.as_ref()).is_absolute() => return None, + _ => {} + } + + // Search for the program in path. + if let Ok(path_var) = crate::env::var("path") { + for p in crate::env::split_paths(&path_var) { + let temp = p.join(prog.as_ref()); + + if let Ok(true) = crate::fs::exists(&temp) { + return Some(temp); + } + } + } + + None +} + #[allow(dead_code)] mod uefi_command_internal { use r_efi::protocols::{loaded_image, simple_text_input, simple_text_output}; From 6f28cd5073b4738b3a630c7903fc0dd1d4f1e1f5 Mon Sep 17 00:00:00 2001 From: Josiah Glosson Date: Mon, 20 Apr 2026 21:48:58 -0500 Subject: [PATCH 03/22] Make Rcs and Arcs use pointer comparison for unsized types --- library/alloc/src/rc.rs | 6 +++--- library/alloc/src/sync.rs | 4 ++-- library/alloctests/tests/arc.rs | 28 ++++++++++++++++++++++++++++ library/alloctests/tests/rc.rs | 28 ++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index 4e6d886658595..d0c415a2ec67a 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -2615,7 +2615,7 @@ impl RcEqIdent for Rc { #[rustc_unsafe_specialization_marker] pub(crate) trait MarkerEq: PartialEq {} -impl MarkerEq for T {} +impl MarkerEq for T {} /// We're doing this specialization here, and not as a more general optimization on `&T`, because it /// would otherwise add a cost to all equality checks on refs. We assume that `Rc`s are used to @@ -2628,12 +2628,12 @@ impl MarkerEq for T {} impl RcEqIdent for Rc { #[inline] fn eq(&self, other: &Rc) -> bool { - Rc::ptr_eq(self, other) || **self == **other + ptr::eq(self.ptr.as_ptr(), other.ptr.as_ptr()) || **self == **other } #[inline] fn ne(&self, other: &Rc) -> bool { - !Rc::ptr_eq(self, other) && **self != **other + !ptr::eq(self.ptr.as_ptr(), other.ptr.as_ptr()) && **self != **other } } diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index 8004dd38d073b..229fcd2b429cf 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -3549,12 +3549,12 @@ impl ArcEqIdent for Arc { impl ArcEqIdent for Arc { #[inline] fn eq(&self, other: &Arc) -> bool { - Arc::ptr_eq(self, other) || **self == **other + ptr::eq(self.ptr.as_ptr(), other.ptr.as_ptr()) || **self == **other } #[inline] fn ne(&self, other: &Arc) -> bool { - !Arc::ptr_eq(self, other) && **self != **other + !ptr::eq(self.ptr.as_ptr(), other.ptr.as_ptr()) && **self != **other } } diff --git a/library/alloctests/tests/arc.rs b/library/alloctests/tests/arc.rs index 00bdf527133f7..a56204187c0a0 100644 --- a/library/alloctests/tests/arc.rs +++ b/library/alloctests/tests/arc.rs @@ -86,6 +86,34 @@ fn eq() { assert_eq!(*x.0.borrow(), 0); } +#[test] +fn eq_unsized() { + #[derive(Eq)] + struct TestEq(RefCell, T); + impl PartialEq for TestEq { + fn eq(&self, other: &TestEq) -> bool { + *self.0.borrow_mut() += 1; + *other.0.borrow_mut() += 1; + true + } + } + let x = Arc::>::new(TestEq(RefCell::new(0), [0, 1, 2])) as Arc>; + assert!(x == x); + assert!(!(x != x)); + assert_eq!(*x.0.borrow(), 0); +} + +#[test] +fn eq_unsized_slice() { + let a: Arc<[()]> = Arc::new([(); 3]); + let ptr: *const () = Arc::into_raw(a.clone()).cast(); + let b: Arc<[()]> = unsafe { Arc::from_raw(core::ptr::slice_from_raw_parts(ptr, 42)) }; + assert!(a == a); + assert!(!(a != a)); + assert!(a != b); + assert!(!(a == b)); +} + // The test code below is identical to that in `rc.rs`. // For better maintainability we therefore define this type alias. type Rc = Arc; diff --git a/library/alloctests/tests/rc.rs b/library/alloctests/tests/rc.rs index bb68eb4ac9e3d..5be0e8f339119 100644 --- a/library/alloctests/tests/rc.rs +++ b/library/alloctests/tests/rc.rs @@ -87,6 +87,34 @@ fn eq() { assert_eq!(*x.0.borrow(), 0); } +#[test] +fn eq_unsized() { + #[derive(Eq)] + struct TestEq(RefCell, T); + impl PartialEq for TestEq { + fn eq(&self, other: &TestEq) -> bool { + *self.0.borrow_mut() += 1; + *other.0.borrow_mut() += 1; + true + } + } + let x = Rc::>::new(TestEq(RefCell::new(0), [0, 1, 2])) as Rc>; + assert!(x == x); + assert!(!(x != x)); + assert_eq!(*x.0.borrow(), 0); +} + +#[test] +fn eq_unsized_slice() { + let a: Rc<[()]> = Rc::new([(); 3]); + let ptr: *const () = Rc::into_raw(a.clone()).cast(); + let b: Rc<[()]> = unsafe { Rc::from_raw(core::ptr::slice_from_raw_parts(ptr, 42)) }; + assert!(a == a); + assert!(!(a != a)); + assert!(a != b); + assert!(!(a == b)); +} + const SHARED_ITER_MAX: u16 = 100; fn assert_trusted_len(_: &I) {} From 2c79213bc5c0c0e071fa73c144efaf563f469234 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Tue, 21 Apr 2026 09:20:02 +0200 Subject: [PATCH 04/22] field representing types: remove explicit `Send` and `Sync` impls Commit cb37ee2c87be ("make field representing types invariant over the base type") made the auto-trait impl work even if `T: !Send` or `T: !Sync` thus we can remove these explicit unsafe impls while FRTs still implement `Send` and `Sync`. --- library/core/src/field.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/library/core/src/field.rs b/library/core/src/field.rs index 0e537e2f92fcf..4c479546a977f 100644 --- a/library/core/src/field.rs +++ b/library/core/src/field.rs @@ -14,18 +14,6 @@ pub struct FieldRepresentingType T>, } -// SAFETY: `FieldRepresentingType` doesn't contain any `T` -unsafe impl Send - for FieldRepresentingType -{ -} - -// SAFETY: `FieldRepresentingType` doesn't contain any `T` -unsafe impl Sync - for FieldRepresentingType -{ -} - impl Copy for FieldRepresentingType { From 59f18c21b08eccd2429a4496d26f0e83b959c09c Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Tue, 21 Apr 2026 09:22:28 +0200 Subject: [PATCH 05/22] field representing types: implement Debug through reflection Using the reflection experiment, we can print the actual names of the field that an FRT is referring to. In case this breaks, there is an alternative implementation in the note comment. --- library/core/src/field.rs | 48 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/library/core/src/field.rs b/library/core/src/field.rs index 4c479546a977f..dfab5a836db3f 100644 --- a/library/core/src/field.rs +++ b/library/core/src/field.rs @@ -1,11 +1,11 @@ //! Field Reflection +use crate::fmt; use crate::marker::PhantomData; /// Field Representing Type #[unstable(feature = "field_representing_type_raw", issue = "none")] #[lang = "field_representing_type"] -#[expect(missing_debug_implementations)] #[fundamental] pub struct FieldRepresentingType { // We want this type to be invariant over `T`, because otherwise `field_of!(Struct<'short>, @@ -14,6 +14,52 @@ pub struct FieldRepresentingType T>, } +impl fmt::Debug + for FieldRepresentingType +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + enum Member { + Name(&'static str), + Index(u32), + } + impl fmt::Display for Member { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Name(name) => fmt::Display::fmt(name, f), + Self::Index(idx) => fmt::Display::fmt(idx, f), + } + } + } + let (variant, field) = const { + use crate::mem::type_info::{Type, TypeKind}; + match Type::of::().kind { + TypeKind::Struct(struct_) => { + (None, Member::Name(struct_.fields[FIELD as usize].name)) + } + TypeKind::Tuple(_) => (None, Member::Index(FIELD)), + TypeKind::Enum(enum_) => { + let variant = &enum_.variants[VARIANT as usize]; + (Some(variant.name), Member::Name(variant.fields[FIELD as usize].name)) + } + TypeKind::Union(union) => (None, Member::Name(union.fields[FIELD as usize].name)), + _ => unreachable!(), + } + }; + let type_name = const { crate::any::type_name::() }; + match variant { + Some(variant) => write!(f, "field_of!({type_name}, {variant}.{field})"), + None => write!(f, "field_of!({type_name}, {field})"), + } + // NOTE: if there are changes in the reflection work and the above no + // longer compiles, then the following debug impl could also work in + // the meantime: + // ```rust + // let type_name = const { type_name::() }; + // write!(f, "field_of!({type_name}, {VARIANT}.{FIELD})") + // ``` + } +} + impl Copy for FieldRepresentingType { From f3719b1686fdf28f7c2e198525cf7487152b4134 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Tue, 21 Apr 2026 09:27:12 +0200 Subject: [PATCH 06/22] field representing types: implement Default --- library/core/src/field.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/core/src/field.rs b/library/core/src/field.rs index dfab5a836db3f..0af1d9902c425 100644 --- a/library/core/src/field.rs +++ b/library/core/src/field.rs @@ -73,6 +73,14 @@ impl Clone } } +impl Default + for FieldRepresentingType +{ + fn default() -> Self { + Self { _phantom: PhantomData::default() } + } +} + /// Expands to the field representing type of the given field. /// /// The container type may be a tuple, `struct`, `union` or `enum`. In the case of an enum, the From 104914e614ee0f90fa6ac5f25dbb7512ffcc1651 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Tue, 21 Apr 2026 09:28:14 +0200 Subject: [PATCH 07/22] field representing types: implement Hash, Eq & Ord --- library/core/src/field.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/library/core/src/field.rs b/library/core/src/field.rs index 0af1d9902c425..2289ce3889557 100644 --- a/library/core/src/field.rs +++ b/library/core/src/field.rs @@ -1,6 +1,7 @@ //! Field Reflection use crate::fmt; +use crate::hash::{Hash, Hasher}; use crate::marker::PhantomData; /// Field Representing Type @@ -81,6 +82,43 @@ impl Default } } +impl Hash + for FieldRepresentingType +{ + fn hash(&self, state: &mut H) { + self._phantom.hash(state); + } +} + +impl PartialEq + for FieldRepresentingType +{ + fn eq(&self, other: &Self) -> bool { + self._phantom == other._phantom + } +} + +impl Eq + for FieldRepresentingType +{ +} + +impl PartialOrd + for FieldRepresentingType +{ + fn partial_cmp(&self, other: &Self) -> Option { + self._phantom.partial_cmp(&other._phantom) + } +} + +impl Ord + for FieldRepresentingType +{ + fn cmp(&self, other: &Self) -> crate::cmp::Ordering { + self._phantom.cmp(&other._phantom) + } +} + /// Expands to the field representing type of the given field. /// /// The container type may be a tuple, `struct`, `union` or `enum`. In the case of an enum, the From ca700edee3aaf5d9f8a63ed41585c32c8a0ac990 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Tue, 21 Apr 2026 09:29:10 +0200 Subject: [PATCH 08/22] field representing types: test all implemented traits --- tests/ui/field_representing_types/traits.rs | 36 +++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/tests/ui/field_representing_types/traits.rs b/tests/ui/field_representing_types/traits.rs index 6b5bb15f9ee9a..bc31376e60ea2 100644 --- a/tests/ui/field_representing_types/traits.rs +++ b/tests/ui/field_representing_types/traits.rs @@ -1,13 +1,17 @@ //@ revisions: old next //@ [next] compile-flags: -Znext-solver //@ run-pass -#![feature(field_projections, freeze)] +#![feature(field_projections, freeze, unsafe_unpin)] #![expect(incomplete_features, dead_code)] use std::field::field_of; -use std::marker::{Freeze, Unpin}; +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::{Freeze, Unpin, UnsafeUnpin}; +use std::panic::{RefUnwindSafe, UnwindSafe}; struct Struct { field: u32, + tail: [u32], } union Union { @@ -19,11 +23,37 @@ enum Enum { Variant2(u32), } -fn assert_traits() {} +type Tuple = ((), usize, String, dyn Debug); + +fn assert_traits< + T: Sized + + Freeze + + RefUnwindSafe + + Send + + Sync + + Unpin + + UnsafeUnpin + + UnwindSafe + + Copy + + Debug + + Default + + Eq + + Hash + + Ord, +>() { +} fn main() { assert_traits::(); + assert_traits::(); + assert_traits::(); + assert_traits::(); assert_traits::(); + + assert_traits::(); + assert_traits::(); + assert_traits::(); + assert_traits::(); } From a041a7609740acc81c8437cb13d95b7320c44bc4 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Tue, 21 Apr 2026 09:10:24 +0200 Subject: [PATCH 09/22] field representing types: add test for unsized types --- .../not-field-if-unsized.next.stderr | 27 +++++++++++++++++++ .../not-field-if-unsized.old.stderr | 27 +++++++++++++++++++ .../not-field-if-unsized.rs | 23 ++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 tests/ui/field_representing_types/not-field-if-unsized.next.stderr create mode 100644 tests/ui/field_representing_types/not-field-if-unsized.old.stderr create mode 100644 tests/ui/field_representing_types/not-field-if-unsized.rs diff --git a/tests/ui/field_representing_types/not-field-if-unsized.next.stderr b/tests/ui/field_representing_types/not-field-if-unsized.next.stderr new file mode 100644 index 0000000000000..e3cee80907720 --- /dev/null +++ b/tests/ui/field_representing_types/not-field-if-unsized.next.stderr @@ -0,0 +1,27 @@ +error[E0277]: the trait bound `field_of!(MyStruct, 0): std::field::Field` is not satisfied + --> $DIR/not-field-if-unsized.rs:17:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyStruct, 0)` + | +note: required by a bound in `assert_field` + --> $DIR/not-field-if-unsized.rs:12:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error[E0277]: the trait bound `field_of!(MyStruct, 1): std::field::Field` is not satisfied + --> $DIR/not-field-if-unsized.rs:21:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyStruct, 1)` + | +note: required by a bound in `assert_field` + --> $DIR/not-field-if-unsized.rs:12:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/field_representing_types/not-field-if-unsized.old.stderr b/tests/ui/field_representing_types/not-field-if-unsized.old.stderr new file mode 100644 index 0000000000000..e3cee80907720 --- /dev/null +++ b/tests/ui/field_representing_types/not-field-if-unsized.old.stderr @@ -0,0 +1,27 @@ +error[E0277]: the trait bound `field_of!(MyStruct, 0): std::field::Field` is not satisfied + --> $DIR/not-field-if-unsized.rs:17:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyStruct, 0)` + | +note: required by a bound in `assert_field` + --> $DIR/not-field-if-unsized.rs:12:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error[E0277]: the trait bound `field_of!(MyStruct, 1): std::field::Field` is not satisfied + --> $DIR/not-field-if-unsized.rs:21:20 + | +LL | assert_field::(); + | ^^^^^^^^^^^^^^^^^^^^^^ the nightly-only, unstable trait `std::field::Field` is not implemented for `field_of!(MyStruct, 1)` + | +note: required by a bound in `assert_field` + --> $DIR/not-field-if-unsized.rs:12:20 + | +LL | fn assert_field() {} + | ^^^^^ required by this bound in `assert_field` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/field_representing_types/not-field-if-unsized.rs b/tests/ui/field_representing_types/not-field-if-unsized.rs new file mode 100644 index 0000000000000..739fc91c29423 --- /dev/null +++ b/tests/ui/field_representing_types/not-field-if-unsized.rs @@ -0,0 +1,23 @@ +//@ revisions: old next +//@ [next] compile-flags: -Znext-solver +#![expect(incomplete_features)] +#![feature(field_projections)] + +use std::field::{Field, field_of}; + +pub trait Trait {} + +pub struct MyStruct(usize, dyn Trait); + +fn assert_field() {} + +fn main() { + // FIXME(FRTs): this requires relaxing the `Base: ?Sized` bound in the + // `Field` trait & compiler changes. + assert_field::(); + //~^ ERROR: the trait bound `field_of!(MyStruct, 0): std::field::Field` is not satisfied [E0277] + + // FIXME(FRTs): improve this error message, point to the `dyn Trait` span. + assert_field::(); + //~^ ERROR: the trait bound `field_of!(MyStruct, 1): std::field::Field` is not satisfied [E0277] +} From 4abc28f57013bd3a3d17c858fca660ca62daeb15 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 22 Apr 2026 10:51:13 -0700 Subject: [PATCH 10/22] `::read_buf`: Clarify local variable name. --- library/std/src/io/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 1166ba8baf430..54b1742c6f258 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -3084,7 +3084,6 @@ impl Read for Take { // SAFETY: no uninit data is written to ibuf let ibuf = unsafe { &mut buf.as_mut()[..limit] }; - let mut sliced_buf: BorrowedBuf<'_> = ibuf.into(); if is_init { @@ -3096,14 +3095,14 @@ impl Read for Take { let mut cursor = sliced_buf.unfilled(); let result = self.inner.read_buf(cursor.reborrow()); - let should_init = cursor.is_init(); + let did_init_up_to_limit = sliced_buf.is_init(); let filled = sliced_buf.len(); // cursor / sliced_buf / ibuf must drop here // Avoid accidentally quadratic behaviour by initializing the whole // cursor if only part of it was initialized. - if should_init { + if did_init_up_to_limit { // SAFETY: no uninit data is written let uninit = unsafe { &mut buf.as_mut()[limit..] }; uninit.write_filled(0); From 71076f2338a7912eed39d8584ab5e3d58145f78a Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 22 Apr 2026 11:14:18 -0700 Subject: [PATCH 11/22] `::read_buf`: Eliminate unneeded local variables. Eliminate `cursor` and `ibuf` as named variables, as their presence makes things more confusing. --- library/std/src/io/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 54b1742c6f258..83b9667f9a573 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -3083,8 +3083,7 @@ impl Read for Take { let is_init = buf.is_init(); // SAFETY: no uninit data is written to ibuf - let ibuf = unsafe { &mut buf.as_mut()[..limit] }; - let mut sliced_buf: BorrowedBuf<'_> = ibuf.into(); + let mut sliced_buf = BorrowedBuf::from(unsafe { &mut buf.as_mut()[..limit] }); if is_init { // SAFETY: `sliced_buf` is a subslice of `buf`, so if `buf` was initialized then @@ -3092,13 +3091,12 @@ impl Read for Take { unsafe { sliced_buf.set_init() }; } - let mut cursor = sliced_buf.unfilled(); - let result = self.inner.read_buf(cursor.reborrow()); + let result = self.inner.read_buf(sliced_buf.unfilled()); let did_init_up_to_limit = sliced_buf.is_init(); let filled = sliced_buf.len(); - // cursor / sliced_buf / ibuf must drop here + // sliced_buf must drop here // Avoid accidentally quadratic behaviour by initializing the whole // cursor if only part of it was initialized. From c716ce5c2eaa741f6a3b7383a09f473df5ed5ad3 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 22 Apr 2026 11:05:35 -0700 Subject: [PATCH 12/22] `::read_buf`: Clarify safety comments and naming. --- library/std/src/io/mod.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 83b9667f9a573..1812bf3ac9276 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -3101,11 +3101,13 @@ impl Read for Take { // Avoid accidentally quadratic behaviour by initializing the whole // cursor if only part of it was initialized. if did_init_up_to_limit { - // SAFETY: no uninit data is written - let uninit = unsafe { &mut buf.as_mut()[limit..] }; - uninit.write_filled(0); - // SAFETY: all bytes that were not initialized by `T::read_buf` - // have just been written to. + // SAFETY: No uninit data will be written. + let unfilled_before_advance = unsafe { buf.as_mut() }; + + unfilled_before_advance[limit..].write_filled(0); + + // SAFETY: `unfilled_before_advance[..limit]` was initialized by `T::read_buf`, and + // `unfilled_before_advance[limit..]` was just initialized. unsafe { buf.set_init() }; } From 3a0a14fd7c187e27eeba7dfe0f7467d18625e162 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Wed, 22 Apr 2026 11:15:27 -0700 Subject: [PATCH 13/22] `::read_buf`: Don't initialize `buf` if it was already initialized. --- library/std/src/io/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 1812bf3ac9276..0a644caa5016b 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -3100,7 +3100,7 @@ impl Read for Take { // Avoid accidentally quadratic behaviour by initializing the whole // cursor if only part of it was initialized. - if did_init_up_to_limit { + if did_init_up_to_limit && !is_init { // SAFETY: No uninit data will be written. let unfilled_before_advance = unsafe { buf.as_mut() }; From 5e00484c38c2ce8084f081678afe47474dbe85ad Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 25 Apr 2026 16:27:05 +0800 Subject: [PATCH 14/22] Avoid misleading closure return type note --- compiler/rustc_hir_typeck/src/coercion.rs | 3 +++ .../closure-return-block-note-issue-155670.rs | 14 ++++++++++++++ .../closure-return-block-note-issue-155670.stderr | 9 +++++++++ 3 files changed, 26 insertions(+) create mode 100644 tests/ui/closures/closure-return-block-note-issue-155670.rs create mode 100644 tests/ui/closures/closure-return-block-note-issue-155670.stderr diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index e34628bad66b4..4dac314b91812 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -2009,6 +2009,9 @@ impl<'tcx> CoerceMany<'tcx> { // note in this case, since it would be incorrect. && let Some(fn_sig) = fcx.body_fn_sig() && fn_sig.output().is_ty_var() + && fcx.ret_coercion.as_ref().is_some_and(|ret_coercion| { + fcx.resolve_vars_if_possible(ret_coercion.borrow().expected_ty()) == expected + }) { err.span_note(sp, format!("return type inferred to be `{expected}` here")); } diff --git a/tests/ui/closures/closure-return-block-note-issue-155670.rs b/tests/ui/closures/closure-return-block-note-issue-155670.rs new file mode 100644 index 0000000000000..fccce9ee54ca2 --- /dev/null +++ b/tests/ui/closures/closure-return-block-note-issue-155670.rs @@ -0,0 +1,14 @@ +struct SmolStr; + +const _: fn() = || { + match Some(()) { + Some(()) => (), + None => return, + }; + let _: String = { + SmolStr + //~^ ERROR mismatched types + }; +}; + +fn main() {} diff --git a/tests/ui/closures/closure-return-block-note-issue-155670.stderr b/tests/ui/closures/closure-return-block-note-issue-155670.stderr new file mode 100644 index 0000000000000..f38d2f14200d4 --- /dev/null +++ b/tests/ui/closures/closure-return-block-note-issue-155670.stderr @@ -0,0 +1,9 @@ +error[E0308]: mismatched types + --> $DIR/closure-return-block-note-issue-155670.rs:9:9 + | +LL | SmolStr + | ^^^^^^^ expected `String`, found `SmolStr` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From 5cd9d929a9b7defd570711a29e70dca5072d2b37 Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Tue, 21 Apr 2026 18:56:59 +0100 Subject: [PATCH 15/22] Avoid Vec allocation in TyCtxt::mk_place_elem `mk_place_elem` appends a single `PlaceElem` to an existing (interned) projection. The current implementation copies the projection into a fresh `Vec`, pushes the new element, and re-interns the slice, which allocates on the heap on every call. Feed the elements through `mk_place_elems_from_iter` so that `CollectAndApply`'s hand-unrolled stack fast path (up to 9 elements, in `rustc_type_ir::interner`) kicks in for the common case of short projections and the `Vec` allocation is skipped entirely. The behavior is identical for longer projections (the fast path falls back to a `Vec` internally). --- compiler/rustc_middle/src/ty/context.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 71b0077b62131..2f83378a2fe4f 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2399,10 +2399,10 @@ impl<'tcx> TyCtxt<'tcx> { /// to build a full `Place` it's just a convenient way to grab a projection and modify it in /// flight. pub fn mk_place_elem(self, place: Place<'tcx>, elem: PlaceElem<'tcx>) -> Place<'tcx> { - let mut projection = place.projection.to_vec(); - projection.push(elem); - - Place { local: place.local, projection: self.mk_place_elems(&projection) } + Place { + local: place.local, + projection: self.mk_place_elems_from_iter(place.projection.iter().chain([elem])), + } } pub fn mk_poly_existential_predicates( From 4fc64f4180b491e3e121abb707d6a01a3e0503c0 Mon Sep 17 00:00:00 2001 From: Usman Akinyemi Date: Thu, 23 Apr 2026 15:48:57 +0530 Subject: [PATCH 16/22] Add boxing suggestions for `impl Trait` return type mismatches Signed-off-by: Usman Akinyemi --- .../src/fn_ctxt/suggestions.rs | 12 ++++++- ...trait-in-return-position-impl-trait.stderr | 7 ++++ ...type-err-cause-on-impl-trait-return.stderr | 36 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 1f08dff18f3ed..68355903c38e9 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -32,7 +32,7 @@ use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _ use tracing::{debug, instrument}; use super::FnCtxt; -use crate::errors; +use crate::errors::{self, SuggestBoxingForReturnImplTrait}; use crate::fn_ctxt::rustc_span::BytePos; use crate::method::probe; use crate::method::probe::{IsSuggestion, Mode, ProbeScope}; @@ -963,6 +963,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } + let trait_def_id = trait_ref.trait_ref.path.res.def_id(); + if self.tcx.is_dyn_compatible(trait_def_id) { + err.subdiagnostic(SuggestBoxingForReturnImplTrait::ChangeReturnType { + start_sp: hir_ty.span.with_hi(hir_ty.span.lo() + BytePos(4)), + end_sp: hir_ty.span.shrink_to_hi(), + }); + + err.note("if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()`"); + } + self.try_suggest_return_impl_trait(err, expected, found, fn_id); self.try_note_caller_chooses_ty_for_ty_param(err, expected, found); return true; diff --git a/tests/ui/impl-trait/dyn-incompatible-trait-in-return-position-impl-trait.stderr b/tests/ui/impl-trait/dyn-incompatible-trait-in-return-position-impl-trait.stderr index 2447a5d8d4b80..6e417b02f5688 100644 --- a/tests/ui/impl-trait/dyn-incompatible-trait-in-return-position-impl-trait.stderr +++ b/tests/ui/impl-trait/dyn-incompatible-trait-in-return-position-impl-trait.stderr @@ -21,6 +21,13 @@ LL | return A; LL | } LL | B | ^ expected `A`, found `B` + | + = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` +help: you could change the return type to be a boxed trait object + | +LL - fn cat() -> impl DynCompatible { +LL + fn cat() -> Box { + | error: aborting due to 2 previous errors diff --git a/tests/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr b/tests/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr index 2a4c5ff4a5bed..a4d084c26e006 100644 --- a/tests/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr +++ b/tests/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr @@ -72,6 +72,12 @@ LL | } LL | 1u32 | ^^^^ expected `i32`, found `u32` | + = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` +help: you could change the return type to be a boxed trait object + | +LL - fn foo() -> impl std::fmt::Display { +LL + fn foo() -> Box { + | help: change the type of the numeric literal from `u32` to `i32` | LL - 1u32 @@ -90,6 +96,12 @@ LL | } else { LL | return 1u32; | ^^^^ expected `i32`, found `u32` | + = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` +help: you could change the return type to be a boxed trait object + | +LL - fn bar() -> impl std::fmt::Display { +LL + fn bar() -> Box { + | help: change the type of the numeric literal from `u32` to `i32` | LL - return 1u32; @@ -108,6 +120,12 @@ LL | } else { LL | 1u32 | ^^^^ expected `i32`, found `u32` | + = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` +help: you could change the return type to be a boxed trait object + | +LL - fn baz() -> impl std::fmt::Display { +LL + fn baz() -> Box { + | help: you can convert a `u32` to an `i32` and panic if the converted value doesn't fit | LL | }.try_into().unwrap() @@ -153,6 +171,12 @@ LL | 0 => return 0i32, LL | _ => 1u32, | ^^^^ expected `i32`, found `u32` | + = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` +help: you could change the return type to be a boxed trait object + | +LL - fn bat() -> impl std::fmt::Display { +LL + fn bat() -> Box { + | help: you can convert a `u32` to an `i32` and panic if the converted value doesn't fit | LL | }.try_into().unwrap() @@ -171,6 +195,12 @@ LL | | _ => 2u32, LL | | } | |_____^ expected `i32`, found `u32` | + = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` +help: you could change the return type to be a boxed trait object + | +LL - fn can() -> impl std::fmt::Display { +LL + fn can() -> Box { + | help: you can convert a `u32` to an `i32` and panic if the converted value doesn't fit | LL | }.try_into().unwrap() @@ -188,6 +218,12 @@ LL | return 0i32; LL | 1u32 | ^^^^ expected `i32`, found `u32` | + = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` +help: you could change the return type to be a boxed trait object + | +LL - fn cat() -> impl std::fmt::Display { +LL + fn cat() -> Box { + | help: you can convert a `u32` to an `i32` and panic if the converted value doesn't fit | LL | }.try_into().unwrap() From 163aedc8737747454e7b2ad16f62601c94260f34 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 26 Apr 2026 13:09:29 +0200 Subject: [PATCH 17/22] Convert attribute `FinalizeFn` to fn pointer --- compiler/rustc_attr_parsing/src/context.rs | 7 +++---- compiler/rustc_attr_parsing/src/interface.rs | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index cebbabfcbf1be..4aa7ebffbd3cf 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -84,8 +84,7 @@ pub(super) struct GroupTypeInnerAccept { pub(crate) type AcceptFn = Box Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser) + Send + Sync>; -pub(crate) type FinalizeFn = - Box) -> Option>; +pub(crate) type FinalizeFn = fn(&mut FinalizeContext<'_, '_, S>) -> Option; macro_rules! attribute_parsers { ( @@ -131,10 +130,10 @@ macro_rules! attribute_parsers { }), safety: <$names as crate::attributes::AttributeParser<$stage>>::SAFETY, allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS, - finalizer: Box::new(|cx| { + finalizer: |cx| { let state = STATE_OBJECT.take(); state.finalize(cx) - }) + } }); } Entry::Occupied(_) => panic!("Attribute {path:?} has multiple accepters"), diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 0dfa67951e629..d350bfee7f348 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -297,7 +297,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { let mut attr_paths: Vec> = Vec::new(); let mut early_parsed_state = EarlyParsedState::default(); - let mut finalizers: Vec<&FinalizeFn> = Vec::with_capacity(attrs.len()); + let mut finalizers: Vec> = Vec::with_capacity(attrs.len()); for attr in attrs { // If we're only looking for a single attribute, skip all the ones we don't care about. @@ -413,7 +413,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { }; (accept.accept_fn)(&mut cx, &args); - finalizers.push(&accept.finalizer); + finalizers.push(accept.finalizer); if !matches!(cx.stage.should_emit(), ShouldEmit::Nothing) { Self::check_target(&accept.allowed_targets, target, &mut cx); From a677828c48af2a7017b63615c0753911e3683d29 Mon Sep 17 00:00:00 2001 From: Usman Akinyemi Date: Sun, 26 Apr 2026 17:15:57 +0530 Subject: [PATCH 18/22] Add boxing suggestions for return expressions in `impl Trait` functions Signed-off-by: Usman Akinyemi --- .../src/fn_ctxt/suggestions.rs | 28 ++++++++++++- ...trait-in-return-position-impl-trait.stderr | 7 +++- ...type-err-cause-on-impl-trait-return.stderr | 42 ++++++++++++++++--- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 68355903c38e9..afd5356d5a1e7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -7,6 +7,7 @@ use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::packed::Pu128; use rustc_errors::{Applicability, Diag, MultiSpan, listify, msg}; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::intravisit::Visitor; use rustc_hir::lang_items::LangItem; use rustc_hir::{ self as hir, Arm, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, @@ -26,6 +27,7 @@ use rustc_session::errors::ExprParenthesesNeeded; use rustc_span::{ExpnKind, Ident, MacroKind, Span, Spanned, Symbol, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::error_reporting::traits::DefIdOrName; +use rustc_trait_selection::error_reporting::traits::suggestions::ReturnsVisitor; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; @@ -970,7 +972,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { end_sp: hir_ty.span.shrink_to_hi(), }); - err.note("if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()`"); + let body = self.tcx.hir_body_owned_by(fn_id); + let mut visitor = ReturnsVisitor::default(); + visitor.visit_body(&body); + + if !visitor.returns.is_empty() { + let starts: Vec = visitor + .returns + .iter() + .filter(|expr| expr.span.can_be_used_for_suggestions()) + .map(|expr| expr.span.shrink_to_lo()) + .collect(); + let ends: Vec = visitor + .returns + .iter() + .filter(|expr| expr.span.can_be_used_for_suggestions()) + .map(|expr| expr.span.shrink_to_hi()) + .collect(); + + if !starts.is_empty() { + err.subdiagnostic(SuggestBoxingForReturnImplTrait::BoxReturnExpr { + starts, + ends, + }); + } + } } self.try_suggest_return_impl_trait(err, expected, found, fn_id); diff --git a/tests/ui/impl-trait/dyn-incompatible-trait-in-return-position-impl-trait.stderr b/tests/ui/impl-trait/dyn-incompatible-trait-in-return-position-impl-trait.stderr index 6e417b02f5688..bba17eb2494e2 100644 --- a/tests/ui/impl-trait/dyn-incompatible-trait-in-return-position-impl-trait.stderr +++ b/tests/ui/impl-trait/dyn-incompatible-trait-in-return-position-impl-trait.stderr @@ -22,12 +22,17 @@ LL | } LL | B | ^ expected `A`, found `B` | - = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` help: you could change the return type to be a boxed trait object | LL - fn cat() -> impl DynCompatible { LL + fn cat() -> Box { | +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ return Box::new(A); +LL | } +LL ~ Box::new(B) + | error: aborting due to 2 previous errors diff --git a/tests/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr b/tests/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr index a4d084c26e006..13a78cb0fcf35 100644 --- a/tests/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr +++ b/tests/ui/impl-trait/point-to-type-err-cause-on-impl-trait-return.stderr @@ -72,12 +72,17 @@ LL | } LL | 1u32 | ^^^^ expected `i32`, found `u32` | - = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` help: you could change the return type to be a boxed trait object | LL - fn foo() -> impl std::fmt::Display { LL + fn foo() -> Box { | +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ return Box::new(0i32); +LL | } +LL ~ Box::new(1u32) + | help: change the type of the numeric literal from `u32` to `i32` | LL - 1u32 @@ -96,12 +101,17 @@ LL | } else { LL | return 1u32; | ^^^^ expected `i32`, found `u32` | - = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` help: you could change the return type to be a boxed trait object | LL - fn bar() -> impl std::fmt::Display { LL + fn bar() -> Box { | +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ return Box::new(0i32); +LL | } else { +LL ~ return Box::new(1u32); + | help: change the type of the numeric literal from `u32` to `i32` | LL - return 1u32; @@ -120,12 +130,17 @@ LL | } else { LL | 1u32 | ^^^^ expected `i32`, found `u32` | - = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` help: you could change the return type to be a boxed trait object | LL - fn baz() -> impl std::fmt::Display { LL + fn baz() -> Box { | +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ return Box::new(0i32); +LL | } else { +LL ~ Box::new(1u32) + | help: you can convert a `u32` to an `i32` and panic if the converted value doesn't fit | LL | }.try_into().unwrap() @@ -171,12 +186,16 @@ LL | 0 => return 0i32, LL | _ => 1u32, | ^^^^ expected `i32`, found `u32` | - = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` help: you could change the return type to be a boxed trait object | LL - fn bat() -> impl std::fmt::Display { LL + fn bat() -> Box { | +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ 0 => return Box::new(0i32), +LL ~ _ => Box::new(1u32), + | help: you can convert a `u32` to an `i32` and panic if the converted value doesn't fit | LL | }.try_into().unwrap() @@ -195,12 +214,17 @@ LL | | _ => 2u32, LL | | } | |_____^ expected `i32`, found `u32` | - = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` help: you could change the return type to be a boxed trait object | LL - fn can() -> impl std::fmt::Display { LL + fn can() -> Box { | +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ 0 => return Box::new(0i32), +LL ~ 1 => Box::new(1u32), +LL ~ _ => Box::new(2u32), + | help: you can convert a `u32` to an `i32` and panic if the converted value doesn't fit | LL | }.try_into().unwrap() @@ -218,12 +242,18 @@ LL | return 0i32; LL | 1u32 | ^^^^ expected `i32`, found `u32` | - = note: if you change the return type to expect trait objects, you'll need to wrap the returned values in `Box::new()` help: you could change the return type to be a boxed trait object | LL - fn cat() -> impl std::fmt::Display { LL + fn cat() -> Box { | +help: if you change the return type to expect trait objects, box the returned expressions + | +LL ~ return Box::new(0i32); +LL | } +LL | _ => { +LL ~ Box::new(1u32) + | help: you can convert a `u32` to an `i32` and panic if the converted value doesn't fit | LL | }.try_into().unwrap() From a7330f48978cd46f9d90e767c2c0cfdae1f7e4a2 Mon Sep 17 00:00:00 2001 From: Sasha Pourcelot Date: Sun, 26 Apr 2026 15:08:44 +0000 Subject: [PATCH 19/22] rustc_attr_parsing: use a `try {}` in `or_malformed` --- .../rustc_attr_parsing/src/attributes/diagnostic/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index e56ed166592aa..b215f77c39adb 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -236,9 +236,11 @@ fn parse_directive_items<'p, S: Stage>( }} macro or_malformed($($code:tt)*) {{ - let Some(ret) = (||{ - Some($($code)*) - })() else { + let Some(ret) = ( + try { + $($code)* + } + ) else { malformed!() }; ret From d9e227e98ac123b9632b61c4c68afba9748ff584 Mon Sep 17 00:00:00 2001 From: jyn Date: Sun, 26 Apr 2026 16:18:45 +0000 Subject: [PATCH 20/22] Fix broken logic in `incremental_verify_ich_failed` --- compiler/rustc_middle/src/error.rs | 2 +- compiler/rustc_middle/src/verify_ich.rs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs index 90af4d785945b..29ec97a6ca592 100644 --- a/compiler/rustc_middle/src/error.rs +++ b/compiler/rustc_middle/src/error.rs @@ -143,7 +143,7 @@ pub(crate) struct Reentrant; #[note( "an ideal reproduction consists of the code before and some patch that then triggers the bug when applied and compiled again" )] -#[note("as a workaround, you can run {$run_cmd} to allow your project to compile")] +#[note("as a workaround, you can {$run_cmd} to allow your project to compile")] pub(crate) struct IncrementCompilation { pub run_cmd: String, pub dep_node: String, diff --git a/compiler/rustc_middle/src/verify_ich.rs b/compiler/rustc_middle/src/verify_ich.rs index a1ab4d8cc4d00..5aa7a1d91f800 100644 --- a/compiler/rustc_middle/src/verify_ich.rs +++ b/compiler/rustc_middle/src/verify_ich.rs @@ -1,6 +1,8 @@ use std::cell::Cell; use rustc_data_structures::fingerprint::Fingerprint; +use rustc_hir::def_id::LOCAL_CRATE; +use rustc_session::utils::was_invoked_from_cargo; use tracing::instrument; use crate::dep_graph::{DepGraphData, SerializedDepNodeIndex}; @@ -66,10 +68,10 @@ fn incremental_verify_ich_failed<'tcx>( if old_in_panic { tcx.dcx().emit_err(crate::error::Reentrant); } else { - let run_cmd = if let Some(crate_name) = &tcx.sess.opts.crate_name { - format!("`cargo clean -p {crate_name}` or `cargo clean`") + let run_cmd = if was_invoked_from_cargo() { + format!("run `cargo clean -p {}` or `cargo clean`", tcx.crate_name(LOCAL_CRATE)) } else { - "`cargo clean`".to_string() + "clean your build cache".to_owned() }; let dep_node = tcx.dep_graph.data().unwrap().prev_node_of(prev_index); From 7151184b46b05a5c96c70d104095c79e6d89aee4 Mon Sep 17 00:00:00 2001 From: jyn Date: Sun, 26 Apr 2026 16:19:11 +0000 Subject: [PATCH 21/22] Add some debugging to `rustc_session` filename handling --- compiler/rustc_session/src/config.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d84bfeb8fff86..cd9d573957f45 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1205,6 +1205,7 @@ impl OutputFilenames { } pub fn interface_path(&self) -> PathBuf { + debug!("using crate_name={} for interface_path", self.crate_stem); self.out_directory.join(format!("lib{}.rs", self.crate_stem)) } @@ -1214,6 +1215,7 @@ impl OutputFilenames { let extension = flavor.extension(); match flavor { OutputType::Metadata => { + debug!("using crate_name={} for {extension}", self.crate_stem); self.out_directory.join(format!("lib{}.{}", self.crate_stem, extension)) } _ => self.with_directory_and_extension(&self.out_directory, extension), @@ -1288,6 +1290,7 @@ impl OutputFilenames { } pub fn with_directory_and_extension(&self, directory: &Path, extension: &str) -> PathBuf { + debug!("using filestem={} for {extension}", self.filestem); let mut path = directory.join(&self.filestem); path.set_extension(extension); path From da545d0856a7d7925586accaa9dafe4068ba86ad Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Sun, 26 Apr 2026 23:20:32 +0000 Subject: [PATCH 22/22] Fix heap overflow in slice::join caused by misbehaving Borrow * Fix heap overflow in slice join via inconsistent Borrow * Update library/alloc/src/str.rs Co-authored-by: Mark Rousskov --- library/alloc/src/str.rs | 32 +++++++++++++++++++++++++++----- library/alloctests/tests/str.rs | 19 +++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index 2966f3ccc1791..98120896bc9b5 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -126,6 +126,26 @@ macro_rules! copy_slice_and_advance { // the bounds for String-join are S: Borrow and for Vec-join Borrow<[T]> // [T] and str both impl AsRef<[T]> for some T // => s.borrow().as_ref() and we always have slices +// +// # Safety notes +// +// `Borrow` is a safe trait, and implementations are not required +// to be deterministic. An inconsistent `Borrow` implementation could return slices +// of different lengths on consecutive calls (e.g. by using interior mutability). +// +// This implementation calls `borrow()` multiple times: +// 1. To calculate `reserved_len`, all elements are borrowed once. +// 2. The first element is borrowed again when copied via `extend_from_slice`. +// 3. Subsequent elements are borrowed a second time when building the mapped iterator. +// +// Risks and Mitigations: +// - If the first element GROWS on the second borrow, the length subtraction underflows. +// We mitigate this by doing a `checked_sub` to panic rather than allowing an underflow +// that fabricates a huge destination slice. +// - If elements 2..N GROW on their second borrow, the target slice bounds set by `checked_sub` +// means that `split_at_mut` inside `copy_slice_and_advance!` will correctly panic. +// - If elements SHRINK on their second borrow, the spare space is never written, and the final +// length set via `set_len` masks trailing uninitialized bytes. #[cfg(not(no_global_oom_handling))] fn join_generic_copy(slice: &[S], sep: &[T]) -> Vec where @@ -161,19 +181,21 @@ where unsafe { let pos = result.len(); - let target = result.spare_capacity_mut().get_unchecked_mut(..reserved_len - pos); + let target_len = reserved_len.checked_sub(pos).expect("inconsistent Borrow implementation"); + let target = result.spare_capacity_mut().get_unchecked_mut(..target_len); // Convert the separator and slices to slices of MaybeUninit - // to simplify implementation in specialize_for_lengths + // to simplify implementation in specialize_for_lengths. let sep_uninit = core::slice::from_raw_parts(sep.as_ptr().cast(), sep.len()); let iter_uninit = iter.map(|it| { let it = it.borrow().as_ref(); core::slice::from_raw_parts(it.as_ptr().cast(), it.len()) }); - // copy separator and slices over without bounds checks - // generate loops with hardcoded offsets for small separators - // massive improvements possible (~ x2) + // copy separator and slices over without bounds checks. + // `specialize_for_lengths!` internally calls `s.borrow()`, but because it uses + // the bounds-checked `split_at_mut` any misbehaving implementation + // will not write out of bounds. let remain = specialize_for_lengths!(sep_uninit, target, iter_uninit; 0, 1, 2, 3, 4); // A weird borrow implementation may return different diff --git a/library/alloctests/tests/str.rs b/library/alloctests/tests/str.rs index c0bcdb8500af6..49baa53c9d3ed 100644 --- a/library/alloctests/tests/str.rs +++ b/library/alloctests/tests/str.rs @@ -194,6 +194,25 @@ fn test_join_issue_80335() { test_join!("0-0-0", arr, "-"); } +#[test] +#[should_panic(expected = "inconsistent Borrow implementation")] +fn test_join_inconsistent_borrow() { + use std::borrow::Borrow; + use std::cell::Cell; + + struct E(Cell); + + impl Borrow for E { + fn borrow(&self) -> &str { + let count = self.0.get(); + self.0.set(count + 1); + if count == 0 { "" } else { "longer string" } + } + } + + let _s = [E(Cell::new(0)), E(Cell::new(0))].join(""); +} + #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_unsafe_slice() {