From ff7b6cc27956d1d51e6651c3207fa97c9496e2bb Mon Sep 17 00:00:00 2001 From: Voxell Paladynee Date: Tue, 11 Nov 2025 04:33:12 +0100 Subject: [PATCH 01/26] doc(core::panic::Location::caller): clarify semantics with visual example without changing doctests --- library/core/src/panic/location.rs | 80 +++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/library/core/src/panic/location.rs b/library/core/src/panic/location.rs index 8176af03d13a..f37f5370997e 100644 --- a/library/core/src/panic/location.rs +++ b/library/core/src/panic/location.rs @@ -105,38 +105,72 @@ impl<'a> Location<'a> { /// ```standalone_crate /// use std::panic::Location; /// - /// /// Returns the [`Location`] at which it is called. + /// /// ``` + /// /// |1 |11 |21 |31 |41 + /// /// +-|---------|---------|---------|---------|-------- + /// /// 15 | #[track_caller] + /// /// 16 | fn new_location() -> &'static Location<'static> { + /// /// 17 | Location::caller() + /// /// | ------------------| the value of this expression depends on the caller, + /// /// | | since the function is marked #[track_caller] + /// /// 18 | } + /// /// ``` /// #[track_caller] - /// fn get_caller_location() -> &'static Location<'static> { + /// fn new_location() -> &'static Location<'static> { /// Location::caller() /// } /// - /// /// Returns a [`Location`] from within this function's definition. - /// fn get_just_one_location() -> &'static Location<'static> { - /// get_caller_location() + /// /// ``` + /// /// |1 |5 |11 |21 |31 |41 |51 + /// /// +-|---|-----|---------|---------|---------|---------|--- + /// /// 29 | fn constant_location() -> &'static Location<'static> { + /// /// 30 | new_location() + /// /// | ^ any invocation of constant_location() points here, + /// /// | no matter the location it is called from + /// /// 31 | } + /// /// ``` + /// fn constant_location() -> &'static Location<'static> { + /// new_location() /// } /// - /// let fixed_location = get_just_one_location(); - /// assert_eq!(fixed_location.file(), file!()); - /// assert_eq!(fixed_location.line(), 14); - /// assert_eq!(fixed_location.column(), 5); + /// fn main() { + /// // |1 |5 |11 |21 |31 |41 |51 + /// // +-|---|-----|---------|---------|---------|---------|--- + /// // 29 | fn constant_location() -> &'static Location<'static> { + /// // 30 | new_location() + /// // | ^ `let constant` points here + /// // 31 | } + /// let constant = constant_location(); + /// assert_eq!(constant.file(), file!()); + /// assert_eq!((constant.line(), constant.column()), (30, 5)); /// - /// // running the same untracked function in a different location gives us the same result - /// let second_fixed_location = get_just_one_location(); - /// assert_eq!(fixed_location.file(), second_fixed_location.file()); - /// assert_eq!(fixed_location.line(), second_fixed_location.line()); - /// assert_eq!(fixed_location.column(), second_fixed_location.column()); + /// let constant_2 = constant_location(); + /// assert_eq!( + /// (constant.file(), constant.line(), constant.column()), + /// (constant_2.file(), constant_2.line(), constant_2.column()) + /// ); /// - /// let this_location = get_caller_location(); - /// assert_eq!(this_location.file(), file!()); - /// assert_eq!(this_location.line(), 28); - /// assert_eq!(this_location.column(), 21); + /// // |1 |11 |16 |21 |31 + /// // +-|---------|----|----|---------|------ + /// // 55 | let here = new_location(); + /// // | ^ `let here` points here, as `new_location()` is the callsite + /// // 56 | assert_eq!(here.file(), file!()); + /// let here = new_location(); + /// assert_eq!(here.file(), file!()); + /// assert_eq!((here.line(), here.column()), (55, 16)); /// - /// // running the tracked function in a different location produces a different value - /// let another_location = get_caller_location(); - /// assert_eq!(this_location.file(), another_location.file()); - /// assert_ne!(this_location.line(), another_location.line()); - /// assert_ne!(this_location.column(), another_location.column()); + /// // |1 |11 |21 ||32 |41 |51 + /// // +-|---------|---------|---------||--------|---------|------ + /// // 64 | let yet_another_location = new_location(); + /// // | ^ `let yet_another_location` points here + /// // 65 | assert_eq!(here.file(), yet_another_location.file()); + /// let yet_another_location = new_location(); + /// assert_eq!(here.file(), yet_another_location.file()); + /// assert_ne!( + /// (here.line(), here.column()), + /// (yet_another_location.line(), yet_another_location.column()) + /// ); + /// } /// ``` #[must_use] #[stable(feature = "track_caller", since = "1.46.0")] From 5a8cf3362c29cbc503d9ea1ec8c119578da38fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=C3=A1n=20de=20B=C3=BArca?= Date: Fri, 7 Nov 2025 09:47:55 -0800 Subject: [PATCH 02/26] alloc: Document panics when allocations will exceed max --- library/alloc/src/string.rs | 21 +++++++++++++++++---- library/alloc/src/vec/mod.rs | 9 +++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 4a2689e01ff1..f5ba71c28833 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -454,6 +454,10 @@ pub const fn new() -> String { /// /// [`new`]: String::new /// + /// # Panics + /// + /// Panics if the capacity exceeds `isize::MAX` _bytes_. + /// /// # Examples /// /// ``` @@ -1079,6 +1083,10 @@ pub const fn as_mut_str(&mut self) -> &mut str { /// Appends a given string slice onto the end of this `String`. /// + /// # Panics + /// + /// Panics if the new capacity exceeds `isize::MAX` _bytes_. + /// /// # Examples /// /// ``` @@ -1101,8 +1109,9 @@ pub fn push_str(&mut self, string: &str) { /// /// # Panics /// - /// Panics if the range has `start_bound > end_bound`, or, if the range is - /// bounded on either end and does not lie on a [`char`] boundary. + /// Panics if the range has `start_bound > end_bound`, if the range is + /// bounded on either end and does not lie on a [`char`] boundary, or if the + /// new capacity exceeds `isize::MAX` bytes. /// /// # Examples /// @@ -1158,7 +1167,7 @@ pub const fn capacity(&self) -> usize { /// /// # Panics /// - /// Panics if the new capacity overflows [`usize`]. + /// Panics if the new capacity exceeds `isize::MAX` _bytes_. /// /// # Examples /// @@ -1208,7 +1217,7 @@ pub fn reserve(&mut self, additional: usize) { /// /// # Panics /// - /// Panics if the new capacity overflows [`usize`]. + /// Panics if the new capacity exceeds `isize::MAX` _bytes_. /// /// # Examples /// @@ -1372,6 +1381,10 @@ pub fn shrink_to(&mut self, min_capacity: usize) { /// Appends the given [`char`] to the end of this `String`. /// + /// # Panics + /// + /// Panics if the new capacity exceeds `isize::MAX` _bytes_. + /// /// # Examples /// /// ``` diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 13d38d3c9609..0c485ed31912 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -3338,6 +3338,10 @@ pub fn resize(&mut self, new_len: usize, value: T) { /// except that it also works with slice elements that are Clone but not Copy. /// If Rust gets specialization this function may be deprecated. /// + /// # Panics + /// + /// Panics if the new capacity exceeds `isize::MAX` _bytes_. + /// /// # Examples /// /// ``` @@ -3359,8 +3363,9 @@ pub fn extend_from_slice(&mut self, other: &[T]) { /// /// # Panics /// - /// Panics if starting index is greater than the end index - /// or if the index is greater than the length of the vector. + /// Panics if starting index is greater than the end index, if the index is + /// greater than the length of the vector, or if the new capacity exceeds + /// `isize::MAX` _bytes_. /// /// # Examples /// From bb239c290c1283d576fde8d87b2e514cf2dec9b9 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 1 Dec 2025 21:51:44 -0500 Subject: [PATCH 03/26] library: Rename `IterRange*` to `Range*Iter` There is a weak convention in the ecosystem that `IterFoos` is an iterator yielding items of type `Foo` (e.g. `bitflags` `IterNames`, `hashbrown` `IterBuckets`), while `FooIter` is an iterator over `Foo` from an `.iter()` or `.into_iter()` method (e.g. `memchr` `OneIter`, `regex` `SetMatchesIter`). Rename `IterRange`, `IterRangeInclusive`, and `IterRangeFrom` to `RangeIter`, `RangeInclusiveIter`, and `RangeInclusiveIter` to match this. Tracking issue: RUST-125687 (`new_range_api`) --- library/core/src/range.rs | 8 +-- library/core/src/range/iter.rs | 54 +++++++++---------- ...ks.rs => fromrangeiter-overflow-checks.rs} | 4 +- .../{iterrangefrom.rs => fromrangeiter.rs} | 0 tests/ui/new-range/enabled.rs | 6 +-- 5 files changed, 36 insertions(+), 36 deletions(-) rename tests/codegen-llvm/{iterrangefrom-overflow-checks.rs => fromrangeiter-overflow-checks.rs} (90%) rename tests/ui/iterators/{iterrangefrom.rs => fromrangeiter.rs} (100%) diff --git a/library/core/src/range.rs b/library/core/src/range.rs index 2df520d35b39..4b87d426bda7 100644 --- a/library/core/src/range.rs +++ b/library/core/src/range.rs @@ -26,7 +26,7 @@ use Bound::{Excluded, Included, Unbounded}; #[doc(inline)] -pub use iter::{IterRange, IterRangeFrom, IterRangeInclusive}; +pub use iter::{RangeFromIter, RangeInclusiveIter, RangeIter}; #[doc(inline)] pub use crate::iter::Step; @@ -89,7 +89,7 @@ impl Range { /// ``` #[unstable(feature = "new_range_api", issue = "125687")] #[inline] - pub fn iter(&self) -> IterRange { + pub fn iter(&self) -> RangeIter { self.clone().into_iter() } } @@ -340,7 +340,7 @@ impl RangeInclusive { /// ``` #[unstable(feature = "new_range_api", issue = "125687")] #[inline] - pub fn iter(&self) -> IterRangeInclusive { + pub fn iter(&self) -> RangeInclusiveIter { self.clone().into_iter() } } @@ -477,7 +477,7 @@ impl RangeFrom { /// ``` #[unstable(feature = "new_range_api", issue = "125687")] #[inline] - pub fn iter(&self) -> IterRangeFrom { + pub fn iter(&self) -> RangeFromIter { self.clone().into_iter() } } diff --git a/library/core/src/range/iter.rs b/library/core/src/range/iter.rs index 9a8824baefe4..6fe5d9b34361 100644 --- a/library/core/src/range/iter.rs +++ b/library/core/src/range/iter.rs @@ -8,9 +8,9 @@ /// By-value [`Range`] iterator. #[unstable(feature = "new_range_api", issue = "125687")] #[derive(Debug, Clone)] -pub struct IterRange(legacy::Range); +pub struct RangeIter(legacy::Range); -impl IterRange { +impl RangeIter { /// Returns the remainder of the range being iterated over. pub fn remainder(self) -> Range { Range { start: self.0.start, end: self.0.end } @@ -23,11 +23,11 @@ macro_rules! unsafe_range_trusted_random_access_impl { ($($t:ty)*) => ($( #[doc(hidden)] #[unstable(feature = "trusted_random_access", issue = "none")] - unsafe impl TrustedRandomAccess for IterRange<$t> {} + unsafe impl TrustedRandomAccess for RangeIter<$t> {} #[doc(hidden)] #[unstable(feature = "trusted_random_access", issue = "none")] - unsafe impl TrustedRandomAccessNoCoerce for IterRange<$t> { + unsafe impl TrustedRandomAccessNoCoerce for RangeIter<$t> { const MAY_HAVE_SIDE_EFFECT: bool = false; } )*) @@ -50,7 +50,7 @@ unsafe impl TrustedRandomAccessNoCoerce for IterRange<$t> { } #[unstable(feature = "new_range_api", issue = "125687")] -impl Iterator for IterRange { +impl Iterator for RangeIter { type Item = A; #[inline] @@ -118,7 +118,7 @@ unsafe fn __iterator_get_unchecked(&mut self, idx: usize) -> Self::Item } #[unstable(feature = "new_range_api", issue = "125687")] -impl DoubleEndedIterator for IterRange { +impl DoubleEndedIterator for RangeIter { #[inline] fn next_back(&mut self) -> Option { self.0.next_back() @@ -136,27 +136,27 @@ fn advance_back_by(&mut self, n: usize) -> Result<(), NonZero> { } #[unstable(feature = "trusted_len", issue = "37572")] -unsafe impl TrustedLen for IterRange {} +unsafe impl TrustedLen for RangeIter {} #[unstable(feature = "new_range_api", issue = "125687")] -impl FusedIterator for IterRange {} +impl FusedIterator for RangeIter {} #[unstable(feature = "new_range_api", issue = "125687")] impl IntoIterator for Range { type Item = A; - type IntoIter = IterRange; + type IntoIter = RangeIter; fn into_iter(self) -> Self::IntoIter { - IterRange(self.into()) + RangeIter(self.into()) } } /// By-value [`RangeInclusive`] iterator. #[unstable(feature = "new_range_api", issue = "125687")] #[derive(Debug, Clone)] -pub struct IterRangeInclusive(legacy::RangeInclusive); +pub struct RangeInclusiveIter(legacy::RangeInclusive); -impl IterRangeInclusive { +impl RangeInclusiveIter { /// Returns the remainder of the range being iterated over. /// /// If the iterator is exhausted or empty, returns `None`. @@ -170,7 +170,7 @@ pub fn remainder(self) -> Option> { } #[unstable(feature = "new_range_api", issue = "125687")] -impl Iterator for IterRangeInclusive { +impl Iterator for RangeInclusiveIter { type Item = A; #[inline] @@ -226,7 +226,7 @@ fn advance_by(&mut self, n: usize) -> Result<(), NonZero> { } #[unstable(feature = "new_range_api", issue = "125687")] -impl DoubleEndedIterator for IterRangeInclusive { +impl DoubleEndedIterator for RangeInclusiveIter { #[inline] fn next_back(&mut self) -> Option { self.0.next_back() @@ -244,18 +244,18 @@ fn advance_back_by(&mut self, n: usize) -> Result<(), NonZero> { } #[unstable(feature = "trusted_len", issue = "37572")] -unsafe impl TrustedLen for IterRangeInclusive {} +unsafe impl TrustedLen for RangeInclusiveIter {} #[unstable(feature = "new_range_api", issue = "125687")] -impl FusedIterator for IterRangeInclusive {} +impl FusedIterator for RangeInclusiveIter {} #[unstable(feature = "new_range_api", issue = "125687")] impl IntoIterator for RangeInclusive { type Item = A; - type IntoIter = IterRangeInclusive; + type IntoIter = RangeInclusiveIter; fn into_iter(self) -> Self::IntoIter { - IterRangeInclusive(self.into()) + RangeInclusiveIter(self.into()) } } @@ -270,14 +270,14 @@ fn into_iter(self) -> Self::IntoIter { macro_rules! range_exact_iter_impl { ($($t:ty)*) => ($( #[unstable(feature = "new_range_api", issue = "125687")] - impl ExactSizeIterator for IterRange<$t> { } + impl ExactSizeIterator for RangeIter<$t> { } )*) } macro_rules! range_incl_exact_iter_impl { ($($t:ty)*) => ($( #[unstable(feature = "new_range_api", issue = "125687")] - impl ExactSizeIterator for IterRangeInclusive<$t> { } + impl ExactSizeIterator for RangeInclusiveIter<$t> { } )*) } @@ -294,14 +294,14 @@ impl ExactSizeIterator for IterRangeInclusive<$t> { } /// By-value [`RangeFrom`] iterator. #[unstable(feature = "new_range_api", issue = "125687")] #[derive(Debug, Clone)] -pub struct IterRangeFrom { +pub struct RangeFromIter { start: A, /// Whether the first element of the iterator has yielded. /// Only used when overflow checks are enabled. first: bool, } -impl IterRangeFrom { +impl RangeFromIter { /// Returns the remainder of the range being iterated over. #[inline] #[rustc_inherit_overflow_checks] @@ -317,7 +317,7 @@ pub fn remainder(self) -> RangeFrom { } #[unstable(feature = "new_range_api", issue = "125687")] -impl Iterator for IterRangeFrom { +impl Iterator for RangeFromIter { type Item = A; #[inline] @@ -366,17 +366,17 @@ fn nth(&mut self, n: usize) -> Option { } #[unstable(feature = "trusted_len", issue = "37572")] -unsafe impl TrustedLen for IterRangeFrom {} +unsafe impl TrustedLen for RangeFromIter {} #[unstable(feature = "new_range_api", issue = "125687")] -impl FusedIterator for IterRangeFrom {} +impl FusedIterator for RangeFromIter {} #[unstable(feature = "new_range_api", issue = "125687")] impl IntoIterator for RangeFrom { type Item = A; - type IntoIter = IterRangeFrom; + type IntoIter = RangeFromIter; fn into_iter(self) -> Self::IntoIter { - IterRangeFrom { start: self.start, first: true } + RangeFromIter { start: self.start, first: true } } } diff --git a/tests/codegen-llvm/iterrangefrom-overflow-checks.rs b/tests/codegen-llvm/fromrangeiter-overflow-checks.rs similarity index 90% rename from tests/codegen-llvm/iterrangefrom-overflow-checks.rs rename to tests/codegen-llvm/fromrangeiter-overflow-checks.rs index 88ff5a8508c8..4d27f118ddd3 100644 --- a/tests/codegen-llvm/iterrangefrom-overflow-checks.rs +++ b/tests/codegen-llvm/fromrangeiter-overflow-checks.rs @@ -11,11 +11,11 @@ #![crate_type = "lib"] #![feature(new_range_api)] -use std::range::{IterRangeFrom, RangeFrom}; +use std::range::{RangeFrom, RangeFromIter}; // CHECK-LABEL: @iterrangefrom_remainder( #[no_mangle] -pub unsafe fn iterrangefrom_remainder(x: IterRangeFrom) -> RangeFrom { +pub unsafe fn iterrangefrom_remainder(x: RangeFromIter) -> RangeFrom { // DEBUG: i32 noundef %x // NOCHECKS: i32 noundef returned %x // DEBUG: br i1 diff --git a/tests/ui/iterators/iterrangefrom.rs b/tests/ui/iterators/fromrangeiter.rs similarity index 100% rename from tests/ui/iterators/iterrangefrom.rs rename to tests/ui/iterators/fromrangeiter.rs diff --git a/tests/ui/new-range/enabled.rs b/tests/ui/new-range/enabled.rs index 5ddbba492e76..140e3b648709 100644 --- a/tests/ui/new-range/enabled.rs +++ b/tests/ui/new-range/enabled.rs @@ -17,8 +17,8 @@ fn main() { let c: core::range::RangeInclusive = 4..=5; let d: core::range::RangeToInclusive = ..=3; - let _: core::range::IterRangeFrom = a.into_iter(); - let _: core::range::IterRange = b.into_iter(); - let _: core::range::IterRangeInclusive = c.into_iter(); + let _: core::range::RangeFromIter = a.into_iter(); + let _: core::range::RangeIter = b.into_iter(); + let _: core::range::RangeInclusiveIter = c.into_iter(); // RangeToInclusive has no Iterator implementation } From e7734099d058ebf5ce89f8b5faeba65faf9942cc Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 30 Nov 2025 11:59:43 +0100 Subject: [PATCH 04/26] float::maximum/minimum: make docs more streamlined --- library/core/src/num/f128.rs | 36 ++++++++++++++++++------------------ library/core/src/num/f16.rs | 36 ++++++++++++++++++------------------ library/core/src/num/f32.rs | 36 ++++++++++++++++++------------------ library/core/src/num/f64.rs | 36 ++++++++++++++++++------------------ 4 files changed, 72 insertions(+), 72 deletions(-) diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index bfe3a501f453..3832d7bb2c39 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -750,8 +750,15 @@ pub const fn min(self, other: f128) -> f128 { /// Returns the maximum of the two numbers, propagating NaN. /// - /// This returns NaN when *either* argument is NaN, as opposed to - /// [`f128::max`] which only returns NaN when *both* arguments are NaN. + /// If at least one of the arguments is NaN, the return value is NaN, with the bit pattern + /// picked using the usual [rules for arithmetic operations](f32#nan-bit-patterns). Furthermore, + /// `-0.0` is considered to be less than `+0.0`, making this function fully deterministic for + /// non-NaN inputs. + /// + /// This is in contrast to [`f128::max`] which only returns NaN when *both* arguments are NaN, + /// and which does not reliably order `-0.0` and `+0.0`. + /// + /// This follows the IEEE 754-2019 semantics for `maximum`. /// /// ``` /// #![feature(f128)] @@ -766,13 +773,6 @@ pub const fn min(self, other: f128) -> f128 { /// assert!(x.maximum(f128::NAN).is_nan()); /// # } /// ``` - /// - /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the greater - /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0. - /// Note that this follows the IEEE 754-2019 semantics for `maximum`. - /// - /// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN - /// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info. #[inline] #[unstable(feature = "f128", issue = "116909")] // #[unstable(feature = "float_minimum_maximum", issue = "91079")] @@ -783,8 +783,15 @@ pub const fn maximum(self, other: f128) -> f128 { /// Returns the minimum of the two numbers, propagating NaN. /// - /// This returns NaN when *either* argument is NaN, as opposed to - /// [`f128::min`] which only returns NaN when *both* arguments are NaN. + /// If at least one of the arguments is NaN, the return value is NaN, with the bit pattern + /// picked using the usual [rules for arithmetic operations](f32#nan-bit-patterns). Furthermore, + /// `-0.0` is considered to be less than `+0.0`, making this function fully deterministic for + /// non-NaN inputs. + /// + /// This is in contrast to [`f128::min`] which only returns NaN when *both* arguments are NaN, + /// and which does not reliably order `-0.0` and `+0.0`. + /// + /// This follows the IEEE 754-2019 semantics for `minimum`. /// /// ``` /// #![feature(f128)] @@ -799,13 +806,6 @@ pub const fn maximum(self, other: f128) -> f128 { /// assert!(x.minimum(f128::NAN).is_nan()); /// # } /// ``` - /// - /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the lesser - /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0. - /// Note that this follows the IEEE 754-2019 semantics for `minimum`. - /// - /// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN - /// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info. #[inline] #[unstable(feature = "f128", issue = "116909")] // #[unstable(feature = "float_minimum_maximum", issue = "91079")] diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index d3a12e94c800..a1b284620cbc 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -741,8 +741,15 @@ pub const fn min(self, other: f16) -> f16 { /// Returns the maximum of the two numbers, propagating NaN. /// - /// This returns NaN when *either* argument is NaN, as opposed to - /// [`f16::max`] which only returns NaN when *both* arguments are NaN. + /// If at least one of the arguments is NaN, the return value is NaN, with the bit pattern + /// picked using the usual [rules for arithmetic operations](f32#nan-bit-patterns). Furthermore, + /// `-0.0` is considered to be less than `+0.0`, making this function fully deterministic for + /// non-NaN inputs. + /// + /// This is in contrast to [`f16::max`] which only returns NaN when *both* arguments are NaN, + /// and which does not reliably order `-0.0` and `+0.0`. + /// + /// This follows the IEEE 754-2019 semantics for `maximum`. /// /// ``` /// #![feature(f16)] @@ -756,13 +763,6 @@ pub const fn min(self, other: f16) -> f16 { /// assert!(x.maximum(f16::NAN).is_nan()); /// # } /// ``` - /// - /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the greater - /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0. - /// Note that this follows the IEEE 754-2019 semantics for `maximum`. - /// - /// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN - /// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info. #[inline] #[unstable(feature = "f16", issue = "116909")] // #[unstable(feature = "float_minimum_maximum", issue = "91079")] @@ -773,8 +773,15 @@ pub const fn maximum(self, other: f16) -> f16 { /// Returns the minimum of the two numbers, propagating NaN. /// - /// This returns NaN when *either* argument is NaN, as opposed to - /// [`f16::min`] which only returns NaN when *both* arguments are NaN. + /// If at least one of the arguments is NaN, the return value is NaN, with the bit pattern + /// picked using the usual [rules for arithmetic operations](f32#nan-bit-patterns). Furthermore, + /// `-0.0` is considered to be less than `+0.0`, making this function fully deterministic for + /// non-NaN inputs. + /// + /// This is in contrast to [`f16::min`] which only returns NaN when *both* arguments are NaN, + /// and which does not reliably order `-0.0` and `+0.0`. + /// + /// This follows the IEEE 754-2019 semantics for `minimum`. /// /// ``` /// #![feature(f16)] @@ -788,13 +795,6 @@ pub const fn maximum(self, other: f16) -> f16 { /// assert!(x.minimum(f16::NAN).is_nan()); /// # } /// ``` - /// - /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the lesser - /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0. - /// Note that this follows the IEEE 754-2019 semantics for `minimum`. - /// - /// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN - /// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info. #[inline] #[unstable(feature = "f16", issue = "116909")] // #[unstable(feature = "float_minimum_maximum", issue = "91079")] diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 7e6a757e5e29..b6ec99436956 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -943,8 +943,15 @@ pub const fn min(self, other: f32) -> f32 { /// Returns the maximum of the two numbers, propagating NaN. /// - /// This returns NaN when *either* argument is NaN, as opposed to - /// [`f32::max`] which only returns NaN when *both* arguments are NaN. + /// If at least one of the arguments is NaN, the return value is NaN, with the bit pattern + /// picked using the usual [rules for arithmetic operations](f32#nan-bit-patterns). Furthermore, + /// `-0.0` is considered to be less than `+0.0`, making this function fully deterministic for + /// non-NaN inputs. + /// + /// This is in contrast to [`f32::max`] which only returns NaN when *both* arguments are NaN, + /// and which does not reliably order `-0.0` and `+0.0`. + /// + /// This follows the IEEE 754-2019 semantics for `maximum`. /// /// ``` /// #![feature(float_minimum_maximum)] @@ -954,13 +961,6 @@ pub const fn min(self, other: f32) -> f32 { /// assert_eq!(x.maximum(y), y); /// assert!(x.maximum(f32::NAN).is_nan()); /// ``` - /// - /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the greater - /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0. - /// Note that this follows the IEEE 754-2019 semantics for `maximum`. - /// - /// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN - /// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info. #[must_use = "this returns the result of the comparison, without modifying either input"] #[unstable(feature = "float_minimum_maximum", issue = "91079")] #[inline] @@ -970,8 +970,15 @@ pub const fn maximum(self, other: f32) -> f32 { /// Returns the minimum of the two numbers, propagating NaN. /// - /// This returns NaN when *either* argument is NaN, as opposed to - /// [`f32::min`] which only returns NaN when *both* arguments are NaN. + /// If at least one of the arguments is NaN, the return value is NaN, with the bit pattern + /// picked using the usual [rules for arithmetic operations](f32#nan-bit-patterns). Furthermore, + /// `-0.0` is considered to be less than `+0.0`, making this function fully deterministic for + /// non-NaN inputs. + /// + /// This is in contrast to [`f32::min`] which only returns NaN when *both* arguments are NaN, + /// and which does not reliably order `-0.0` and `+0.0`. + /// + /// This follows the IEEE 754-2019 semantics for `minimum`. /// /// ``` /// #![feature(float_minimum_maximum)] @@ -981,13 +988,6 @@ pub const fn maximum(self, other: f32) -> f32 { /// assert_eq!(x.minimum(y), x); /// assert!(x.minimum(f32::NAN).is_nan()); /// ``` - /// - /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the lesser - /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0. - /// Note that this follows the IEEE 754-2019 semantics for `minimum`. - /// - /// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN - /// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info. #[must_use = "this returns the result of the comparison, without modifying either input"] #[unstable(feature = "float_minimum_maximum", issue = "91079")] #[inline] diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 854bdcf39d09..42726fa57dc5 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -961,8 +961,15 @@ pub const fn min(self, other: f64) -> f64 { /// Returns the maximum of the two numbers, propagating NaN. /// - /// This returns NaN when *either* argument is NaN, as opposed to - /// [`f64::max`] which only returns NaN when *both* arguments are NaN. + /// If at least one of the arguments is NaN, the return value is NaN, with the bit pattern + /// picked using the usual [rules for arithmetic operations](f32#nan-bit-patterns). Furthermore, + /// `-0.0` is considered to be less than `+0.0`, making this function fully deterministic for + /// non-NaN inputs. + /// + /// This is in contrast to [`f64::max`] which only returns NaN when *both* arguments are NaN, + /// and which does not reliably order `-0.0` and `+0.0`. + /// + /// This follows the IEEE 754-2019 semantics for `maximum`. /// /// ``` /// #![feature(float_minimum_maximum)] @@ -972,13 +979,6 @@ pub const fn min(self, other: f64) -> f64 { /// assert_eq!(x.maximum(y), y); /// assert!(x.maximum(f64::NAN).is_nan()); /// ``` - /// - /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the greater - /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0. - /// Note that this follows the IEEE 754-2019 semantics for `maximum`. - /// - /// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN - /// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info. #[must_use = "this returns the result of the comparison, without modifying either input"] #[unstable(feature = "float_minimum_maximum", issue = "91079")] #[inline] @@ -988,8 +988,15 @@ pub const fn maximum(self, other: f64) -> f64 { /// Returns the minimum of the two numbers, propagating NaN. /// - /// This returns NaN when *either* argument is NaN, as opposed to - /// [`f64::min`] which only returns NaN when *both* arguments are NaN. + /// If at least one of the arguments is NaN, the return value is NaN, with the bit pattern + /// picked using the usual [rules for arithmetic operations](f32#nan-bit-patterns). Furthermore, + /// `-0.0` is considered to be less than `+0.0`, making this function fully deterministic for + /// non-NaN inputs. + /// + /// This is in contrast to [`f64::min`] which only returns NaN when *both* arguments are NaN, + /// and which does not reliably order `-0.0` and `+0.0`. + /// + /// This follows the IEEE 754-2019 semantics for `minimum`. /// /// ``` /// #![feature(float_minimum_maximum)] @@ -999,13 +1006,6 @@ pub const fn maximum(self, other: f64) -> f64 { /// assert_eq!(x.minimum(y), x); /// assert!(x.minimum(f64::NAN).is_nan()); /// ``` - /// - /// If one of the arguments is NaN, then NaN is returned. Otherwise this returns the lesser - /// of the two numbers. For this operation, -0.0 is considered to be less than +0.0. - /// Note that this follows the IEEE 754-2019 semantics for `minimum`. - /// - /// Also note that "propagation" of NaNs here doesn't necessarily mean that the bitpattern of a NaN - /// operand is conserved; see the [specification of NaN bit patterns](f32#nan-bit-patterns) for more info. #[must_use = "this returns the result of the comparison, without modifying either input"] #[unstable(feature = "float_minimum_maximum", issue = "91079")] #[inline] From 6f632ef2430f34523a19b90f574e288646225e9b Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Wed, 8 Oct 2025 03:25:43 +0100 Subject: [PATCH 05/26] non-behaviour changing cleanups --- compiler/rustc_hir_typeck/src/coercion.rs | 241 +++++++----------- ...structural_identity_dependent_reborrows.rs | 24 ++ ...ctural_identity_dependent_reborrows.stderr | 25 ++ 3 files changed, 140 insertions(+), 150 deletions(-) create mode 100644 tests/ui/coercion/structural_identity_dependent_reborrows.rs create mode 100644 tests/ui/coercion/structural_identity_dependent_reborrows.stderr diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 009caad51eac..bcff4eeb71b3 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -179,7 +179,7 @@ fn unify_and( }) } - #[instrument(skip(self))] + #[instrument(skip(self), ret)] fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { // First, remove any resolved type variables (at the top level, at least): let a = self.shallow_resolve(a); @@ -223,21 +223,20 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { } } - // Examine the supertype and consider type-specific coercions, such - // as auto-borrowing, coercing pointer mutability, a `dyn*` coercion, - // or pin-ergonomics. + // Examine the target type and consider type-specific coercions, such + // as auto-borrowing, coercing pointer mutability, or pin-ergonomics. match *b.kind() { ty::RawPtr(_, b_mutbl) => { - return self.coerce_raw_ptr(a, b, b_mutbl); + return self.coerce_to_raw_ptr(a, b, b_mutbl); } ty::Ref(r_b, _, mutbl_b) => { - return self.coerce_borrowed_pointer(a, b, r_b, mutbl_b); + return self.coerce_to_ref(a, b, mutbl_b, r_b); } ty::Adt(pin, _) if self.tcx.features().pin_ergonomics() && self.tcx.is_lang_item(pin.did(), hir::LangItem::Pin) => { - let pin_coerce = self.commit_if_ok(|_| self.coerce_pin_ref(a, b)); + let pin_coerce = self.commit_if_ok(|_| self.coerce_to_pin_ref(a, b)); if pin_coerce.is_ok() { return pin_coerce; } @@ -281,22 +280,21 @@ fn coerce_from_inference_variable(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResu debug_assert!(self.shallow_resolve(b) == b); if b.is_ty_var() { - // Two unresolved type variables: create a `Coerce` predicate. - let target_ty = if self.use_lub { self.next_ty_var(self.cause.span) } else { b }; - let mut obligations = PredicateObligations::with_capacity(2); - for &source_ty in &[a, b] { - if source_ty != target_ty { - obligations.push(Obligation::new( - self.tcx(), - self.cause.clone(), - self.param_env, - ty::Binder::dummy(ty::PredicateKind::Coerce(ty::CoercePredicate { - a: source_ty, - b: target_ty, - })), - )); - } + let mut push_coerce_obligation = |a, b| { + obligations.push(Obligation::new( + self.tcx(), + self.cause.clone(), + self.param_env, + ty::Binder::dummy(ty::PredicateKind::Coerce(ty::CoercePredicate { a, b })), + )); + }; + + let target_ty = self.use_lub.then(|| self.next_ty_var(self.cause.span)).unwrap_or(b); + + push_coerce_obligation(a, target_ty); + if self.use_lub { + push_coerce_obligation(b, target_ty); } debug!( @@ -311,159 +309,99 @@ fn coerce_from_inference_variable(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResu } } - /// Reborrows `&mut A` to `&mut B` and `&(mut) A` to `&B`. - /// To match `A` with `B`, autoderef will be performed, - /// calling `deref`/`deref_mut` where necessary. - fn coerce_borrowed_pointer( + /// Handles coercing some arbitrary type `a` to some reference (`b`). This + /// handles a few cases: + /// - Introducing reborrows to give more flexible lifetimes + /// - Deref coercions to allow `&T` to coerce to `&T::Target` + /// - Coercing mutable references to immutable references + /// These coercions can be freely intermixed, for example we are able to + /// coerce `&mut T` to `&mut T::Target`. + fn coerce_to_ref( &self, a: Ty<'tcx>, b: Ty<'tcx>, - r_b: ty::Region<'tcx>, mutbl_b: hir::Mutability, + r_b: ty::Region<'tcx>, ) -> CoerceResult<'tcx> { - debug!("coerce_borrowed_pointer(a={:?}, b={:?})", a, b); + debug!("coerce_to_ref(a={:?}, b={:?})", a, b); debug_assert!(self.shallow_resolve(a) == a); debug_assert!(self.shallow_resolve(b) == b); - // If we have a parameter of type `&M T_a` and the value - // provided is `expr`, we will be adding an implicit borrow, - // meaning that we convert `f(expr)` to `f(&M *expr)`. Therefore, - // to type check, we will construct the type that `&M*expr` would - // yield. - let (r_a, mt_a) = match *a.kind() { ty::Ref(r_a, ty, mutbl) => { - let mt_a = ty::TypeAndMut { ty, mutbl }; - coerce_mutbls(mt_a.mutbl, mutbl_b)?; - (r_a, mt_a) + coerce_mutbls(mutbl, mutbl_b)?; + (r_a, ty::TypeAndMut { ty, mutbl }) } _ => return self.unify(a, b), }; - let span = self.cause.span; - + // Look at each step in the `Deref` chain and check if + // any of the autoref'd `Target` types unify with the + // coercion target. + // + // For example when coercing from `&mut Vec` to `&M [T]` we + // have three deref steps: + // 1. `&mut Vec`, skip autoref + // 2. `Vec`, autoref'd ty: `&M Vec` + // - `&M Vec` does not unify with `&M [T]` + // 3. `[T]`, autoref'd ty: `&M [T]` + // - `&M [T]` does unify with `&M [T]` let mut first_error = None; let mut r_borrow_var = None; - let mut autoderef = self.autoderef(span, a); - let mut found = None; - - for (referent_ty, autoderefs) in autoderef.by_ref() { + let mut autoderef = self.autoderef(self.cause.span, a); + let found = autoderef.by_ref().find_map(|(deref_ty, autoderefs)| { if autoderefs == 0 { - // Don't let this pass, otherwise it would cause - // &T to autoref to &&T. - continue; + // Don't autoref the first step as otherwise we'd allow + // coercing `&T` to `&&T`. + return None; } - // At this point, we have deref'd `a` to `referent_ty`. So - // imagine we are coercing from `&'a mut Vec` to `&'b mut [T]`. - // In the autoderef loop for `&'a mut Vec`, we would get - // three callbacks: + // The logic here really shouldn't exist. We don't care about free + // lifetimes during HIR typeck. Unfortunately later parts of this + // function rely on structural identity of the autoref'd deref'd ty. // - // - `&'a mut Vec` -- 0 derefs, just ignore it - // - `Vec` -- 1 deref - // - `[T]` -- 2 deref - // - // At each point after the first callback, we want to - // check to see whether this would match out target type - // (`&'b mut [T]`) if we autoref'd it. We can't just - // compare the referent types, though, because we still - // have to consider the mutability. E.g., in the case - // we've been considering, we have an `&mut` reference, so - // the `T` in `[T]` needs to be unified with equality. - // - // Therefore, we construct reference types reflecting what - // the types will be after we do the final auto-ref and - // compare those. Note that this means we use the target - // mutability [1], since it may be that we are coercing - // from `&mut T` to `&U`. - // - // One fine point concerns the region that we use. We - // choose the region such that the region of the final - // type that results from `unify` will be the region we - // want for the autoref: - // - // - if in sub mode, that means we want to use `'b` (the - // region from the target reference) for both - // pointers [2]. This is because sub mode (somewhat - // arbitrarily) returns the subtype region. In the case - // where we are coercing to a target type, we know we - // want to use that target type region (`'b`) because -- - // for the program to type-check -- it must be the - // smaller of the two. - // - One fine point. It may be surprising that we can - // use `'b` without relating `'a` and `'b`. The reason - // that this is ok is that what we produce is - // effectively a `&'b *x` expression (if you could - // annotate the region of a borrow), and regionck has - // code that adds edges from the region of a borrow - // (`'b`, here) into the regions in the borrowed - // expression (`*x`, here). (Search for "link".) - // - if in lub mode, things can get fairly complicated. The - // easiest thing is just to make a fresh - // region variable [4], which effectively means we defer - // the decision to region inference (and regionck, which will add - // some more edges to this variable). However, this can wind up - // creating a crippling number of variables in some cases -- - // e.g., #32278 -- so we optimize one particular case [3]. - // Let me try to explain with some examples: - // - The "running example" above represents the simple case, - // where we have one `&` reference at the outer level and - // ownership all the rest of the way down. In this case, - // we want `LUB('a, 'b)` as the resulting region. - // - However, if there are nested borrows, that region is - // too strong. Consider a coercion from `&'a &'x Rc` to - // `&'b T`. In this case, `'a` is actually irrelevant. - // The pointer we want is `LUB('x, 'b`). If we choose `LUB('a,'b)` - // we get spurious errors (`ui/regions-lub-ref-ref-rc.rs`). - // (The errors actually show up in borrowck, typically, because - // this extra edge causes the region `'a` to be inferred to something - // too big, which then results in borrowck errors.) - // - We could track the innermost shared reference, but there is already - // code in regionck that has the job of creating links between - // the region of a borrow and the regions in the thing being - // borrowed (here, `'a` and `'x`), and it knows how to handle - // all the various cases. So instead we just make a region variable - // and let regionck figure it out. + // This means that what region we use here actually impacts whether + // we emit a reborrow coercion or not which can affect diagnostics + // and capture analysis (which in turn affects borrowck). let r = if !self.use_lub { - r_b // [2] above + r_b } else if autoderefs == 1 { - r_a // [3] above + r_a } else { if r_borrow_var.is_none() { // create var lazily, at most once - let coercion = RegionVariableOrigin::Coercion(span); + let coercion = RegionVariableOrigin::Coercion(self.cause.span); let r = self.next_region_var(coercion); - r_borrow_var = Some(r); // [4] above + r_borrow_var = Some(r); } r_borrow_var.unwrap() }; - let derefd_ty_a = Ty::new_ref( - self.tcx, - r, - referent_ty, - mutbl_b, // [1] above - ); - match self.unify_raw(derefd_ty_a, b) { - Ok(ok) => { - found = Some(ok); - break; - } + + let autorefd_deref_ty = Ty::new_ref(self.tcx, r, deref_ty, mutbl_b); + + // Note that we unify the autoref'd `Target` type with `b` rather than + // the `Target` type with the pointee of `b`. This is necessary + // to properly account for the differing variances of the pointees + // of `&` vs `&mut` references. + match self.unify_raw(autorefd_deref_ty, b) { + Ok(ok) => Some(ok), Err(err) => { if first_error.is_none() { first_error = Some(err); } + None } } - } + }); // Extract type or return an error. We return the first error // we got, which should be from relating the "base" type // (e.g., in example above, the failure from relating `Vec` // to the target type), since that should be the least // confusing. - let Some(InferOk { value: ty, mut obligations }) = found else { + let Some(InferOk { value: coerced_a, mut obligations }) = found else { if let Some(first_error) = first_error { - debug!("coerce_borrowed_pointer: failed with err = {:?}", first_error); + debug!("coerce_to_ref: failed with err = {:?}", first_error); return Err(first_error); } else { // This may happen in the new trait solver since autoderef requires @@ -475,11 +413,15 @@ fn coerce_borrowed_pointer( } }; - if ty == a && mt_a.mutbl.is_not() && autoderef.step_count() == 1 { + if coerced_a == a && mt_a.mutbl.is_not() && autoderef.step_count() == 1 { // As a special case, if we would produce `&'a *x`, that's // a total no-op. We end up with the type `&'a T` just as - // we started with. In that case, just skip it - // altogether. This is just an optimization. + // we started with. In that case, just skip it altogether. + // + // Unfortunately, this can actually effect capture analysis + // which in turn means this effects borrow checking. This can + // also effect diagnostics. + // FIXME(BoxyUwU): we should always emit reborrow coercions // // Note that for `&mut`, we DO want to reborrow -- // otherwise, this would be a move, which might be an @@ -488,7 +430,7 @@ fn coerce_borrowed_pointer( // `self.x`, but we auto-coerce it to `foo(&mut *self.x)`, // which is a borrow. assert!(mutbl_b.is_not()); // can only coerce &T -> &U - return success(vec![], ty, obligations); + return success(vec![], coerced_a, obligations); } let InferOk { value: mut adjustments, obligations: o } = @@ -496,17 +438,17 @@ fn coerce_borrowed_pointer( obligations.extend(o); obligations.extend(autoderef.into_obligations()); - // Now apply the autoref. We have to extract the region out of - // the final ref type we got. - let ty::Ref(..) = ty.kind() else { - span_bug!(span, "expected a ref type, got {:?}", ty); + // Now apply the autoref + let ty::Ref(..) = coerced_a.kind() else { + span_bug!(self.cause.span, "expected a ref type, got {:?}", coerced_a); }; let mutbl = AutoBorrowMutability::new(mutbl_b, self.allow_two_phase); - adjustments.push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), target: ty }); + adjustments + .push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), target: coerced_a }); - debug!("coerce_borrowed_pointer: succeeded ty={:?} adjustments={:?}", ty, adjustments); + debug!("coerce_to_ref: succeeded coerced_a={:?} adjustments={:?}", coerced_a, adjustments); - success(adjustments, ty, obligations) + success(adjustments, coerced_a, obligations) } /// Performs [unsized coercion] by emulating a fulfillment loop on a @@ -569,9 +511,8 @@ fn coerce_unsized(&self, source: Ty<'tcx>, target: Ty<'tcx>) -> CoerceResult<'tc | ty::Tuple(_) => return Err(TypeError::Mismatch), _ => {} } - // Additionally, we ignore `&str -> &str` coercions, which happen very - // commonly since strings are one of the most used argument types in Rust, - // we do coercions when type checking call expressions. + // `&str: CoerceUnsized<&str>` does not hold but is encountered frequently + // so we fast path bail out here if let ty::Ref(_, source_pointee, ty::Mutability::Not) = *source.kind() && source_pointee.is_str() && let ty::Ref(_, target_pointee, ty::Mutability::Not) = *target.kind() @@ -810,7 +751,7 @@ fn coerce_unsized_old_solver( /// - `Pin>` as `Pin<&T>` /// - `Pin>` as `Pin<&mut T>` #[instrument(skip(self), level = "trace")] - fn coerce_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { + fn coerce_to_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { debug_assert!(self.shallow_resolve(a) == a); debug_assert!(self.shallow_resolve(b) == b); @@ -1013,13 +954,13 @@ fn coerce_closure_to_fn( } } - fn coerce_raw_ptr( + fn coerce_to_raw_ptr( &self, a: Ty<'tcx>, b: Ty<'tcx>, mutbl_b: hir::Mutability, ) -> CoerceResult<'tcx> { - debug!("coerce_raw_ptr(a={:?}, b={:?})", a, b); + debug!("coerce_to_raw_ptr(a={:?}, b={:?})", a, b); debug_assert!(self.shallow_resolve(a) == a); debug_assert!(self.shallow_resolve(b) == b); diff --git a/tests/ui/coercion/structural_identity_dependent_reborrows.rs b/tests/ui/coercion/structural_identity_dependent_reborrows.rs new file mode 100644 index 000000000000..60cf28aaf303 --- /dev/null +++ b/tests/ui/coercion/structural_identity_dependent_reborrows.rs @@ -0,0 +1,24 @@ +//@ edition: 2024 + +// We avoid emitting reborrow coercions if it seems like it would +// not result in a different lifetime on the borrow. This can effect +// capture analysis resulting in borrow checking errors. + +fn foo<'a>(b: &'a ()) -> impl Fn() { + || { + expected::<&()>(b); + } +} + +// No reborrow of `b` is emitted which means our closure captures +// `b` by ref resulting in an upvar of `&&'a ()` +fn bar<'a>(b: &'a ()) -> impl Fn() { + || { + //~^ ERROR: closure may outlive the current function + expected::<&'a ()>(b); + } +} + +fn expected(_: T) {} + +fn main() {} diff --git a/tests/ui/coercion/structural_identity_dependent_reborrows.stderr b/tests/ui/coercion/structural_identity_dependent_reborrows.stderr new file mode 100644 index 000000000000..d598a4fa7633 --- /dev/null +++ b/tests/ui/coercion/structural_identity_dependent_reborrows.stderr @@ -0,0 +1,25 @@ +error[E0373]: closure may outlive the current function, but it borrows `b`, which is owned by the current function + --> $DIR/structural_identity_dependent_reborrows.rs:16:5 + | +LL | || { + | ^^ may outlive borrowed value `b` +LL | +LL | expected::<&'a ()>(b); + | - `b` is borrowed here + | +note: closure is returned here + --> $DIR/structural_identity_dependent_reborrows.rs:16:5 + | +LL | / || { +LL | | +LL | | expected::<&'a ()>(b); +LL | | } + | |_____^ +help: to force the closure to take ownership of `b` (and any other referenced variables), use the `move` keyword + | +LL | move || { + | ++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0373`. From 9a1b2e8846fbdcc36f2b435cb6d3e0e661046108 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 10 Oct 2025 18:03:53 +0100 Subject: [PATCH 06/26] remove normalize call --- compiler/rustc_hir_typeck/src/coercion.rs | 12 ++--- tests/crashes/132765.rs | 12 ----- .../hr_alias_normalization_leaking_vars.rs | 36 +++++++++++++++ ...hr_alias_normalization_leaking_vars.stderr | 46 +++++++++++++++++++ 4 files changed, 87 insertions(+), 19 deletions(-) delete mode 100644 tests/crashes/132765.rs create mode 100644 tests/ui/coercion/hr_alias_normalization_leaking_vars.rs create mode 100644 tests/ui/coercion/hr_alias_normalization_leaking_vars.stderr diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index bcff4eeb71b3..32104a5c0cf7 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -856,9 +856,6 @@ fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { debug_assert!(self.shallow_resolve(a) == a); debug_assert!(self.shallow_resolve(b) == b); - let InferOk { value: b, mut obligations } = - self.at(&self.cause, self.param_env).normalize(b); - match b.kind() { ty::FnPtr(_, b_hdr) => { let mut a_sig = a.fn_sig(self.tcx); @@ -890,9 +887,10 @@ fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { } } - let InferOk { value: a_sig, obligations: o1 } = + // FIXME: we shouldn't be normalizing here as coercion is inside of + // a probe. This can probably cause ICEs. + let InferOk { value: a_sig, mut obligations } = self.at(&self.cause, self.param_env).normalize(a_sig); - obligations.extend(o1); let InferOk { value, obligations: o2 } = self.coerce_from_safe_fn( a_sig, @@ -907,8 +905,8 @@ fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { } } - /// Attempts to coerce from the type of a non-capturing closure - /// into a function pointer. + /// Attempts to coerce from a closure to a function pointer. Fails + /// if the closure has any upvars. fn coerce_closure_to_fn( &self, a: Ty<'tcx>, diff --git a/tests/crashes/132765.rs b/tests/crashes/132765.rs deleted file mode 100644 index 01e8fdaacff7..000000000000 --- a/tests/crashes/132765.rs +++ /dev/null @@ -1,12 +0,0 @@ -//@ known-bug: #132765 - -trait LendingIterator { - type Item<'q>; - fn for_each(&self, _f: Box)>) {} -} - -fn f(_: ()) {} - -fn main() { - LendingIterator::for_each(&(), f); -} diff --git a/tests/ui/coercion/hr_alias_normalization_leaking_vars.rs b/tests/ui/coercion/hr_alias_normalization_leaking_vars.rs new file mode 100644 index 000000000000..43678fd876fb --- /dev/null +++ b/tests/ui/coercion/hr_alias_normalization_leaking_vars.rs @@ -0,0 +1,36 @@ +// We have two function parameters with types: +// - `&?0` +// - `Box fn(>::Item)>` +// +// As the alias in the second parameter has a `?0` it is an ambig +// alias, and as it references bound vars it can't be normalized to +// an infer var. +// +// When checking function arguments we try to coerce both: +// - `&()` to `&?0` +// - `FnDef(f)` to `Box fn(>::Item)>` +// +// The first coercion infers `?0=()`. Previously when handling +// the second coercion we wound *re-normalize* the alias, which +// now that `?0` has been inferred allowed us to determine this +// alias is not wellformed and normalize it to some infer var `?1`. +// +// We would then see that `FnDef(f)` can't be coerced to `Box` +// and return a `TypeError` referencing this new variable `?1`. This +// then caused ICEs as diagnostics would encounter inferences variables +// from the result of normalization inside of the probe used be coercion. + + +trait LendingIterator { + type Item<'q>; + fn for_each(&self, _f: Box)>) {} +} + +fn f(_: ()) {} + +fn main() { + LendingIterator::for_each(&(), f); + //~^ ERROR: the trait bound `(): LendingIterator` is not satisfied + //~| ERROR: the trait bound `(): LendingIterator` is not satisfied + //~| ERROR: mismatched types +} diff --git a/tests/ui/coercion/hr_alias_normalization_leaking_vars.stderr b/tests/ui/coercion/hr_alias_normalization_leaking_vars.stderr new file mode 100644 index 000000000000..143cc6b29735 --- /dev/null +++ b/tests/ui/coercion/hr_alias_normalization_leaking_vars.stderr @@ -0,0 +1,46 @@ +error[E0277]: the trait bound `(): LendingIterator` is not satisfied + --> $DIR/hr_alias_normalization_leaking_vars.rs:32:31 + | +LL | LendingIterator::for_each(&(), f); + | ------------------------- ^^^ the trait `LendingIterator` is not implemented for `()` + | | + | required by a bound introduced by this call + | +help: this trait has no implementations, consider adding one + --> $DIR/hr_alias_normalization_leaking_vars.rs:24:1 + | +LL | trait LendingIterator { + | ^^^^^^^^^^^^^^^^^^^^^ + +error[E0308]: mismatched types + --> $DIR/hr_alias_normalization_leaking_vars.rs:32:36 + | +LL | LendingIterator::for_each(&(), f); + | ------------------------- ^ expected `Box`, found fn item + | | + | arguments to this function are incorrect + | + = note: expected struct `Box fn(<() as LendingIterator>::Item<'a>)>` + found fn item `fn(()) {f}` +note: method defined here + --> $DIR/hr_alias_normalization_leaking_vars.rs:26:8 + | +LL | fn for_each(&self, _f: Box)>) {} + | ^^^^^^^^ --------------------------- + +error[E0277]: the trait bound `(): LendingIterator` is not satisfied + --> $DIR/hr_alias_normalization_leaking_vars.rs:32:36 + | +LL | LendingIterator::for_each(&(), f); + | ^ the trait `LendingIterator` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/hr_alias_normalization_leaking_vars.rs:24:1 + | +LL | trait LendingIterator { + | ^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0277, E0308. +For more information about an error, try `rustc --explain E0277`. From 12173eba3e86f0c6e2395b418fd2ce5dacd2d58e Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Thu, 6 Nov 2025 18:49:51 +0000 Subject: [PATCH 07/26] explicit leak check enum --- compiler/rustc_hir_typeck/src/coercion.rs | 133 +++++++++++++--------- 1 file changed, 82 insertions(+), 51 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 32104a5c0cf7..9c4c8edd10b7 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -113,6 +113,11 @@ fn success<'tcx>( Ok(InferOk { value: (adj, target), obligations }) } +enum LeakCheck { + Yes, + Default, +} + impl<'f, 'tcx> Coerce<'f, 'tcx> { fn new( fcx: &'f FnCtxt<'f, 'tcx>, @@ -123,9 +128,16 @@ fn new( Coerce { fcx, cause, allow_two_phase, use_lub: false, coerce_never } } - fn unify_raw(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> InferResult<'tcx, Ty<'tcx>> { + fn unify_raw( + &self, + a: Ty<'tcx>, + b: Ty<'tcx>, + leak_check: LeakCheck, + ) -> InferResult<'tcx, Ty<'tcx>> { debug!("unify(a: {:?}, b: {:?}, use_lub: {})", a, b, self.use_lub); - self.commit_if_ok(|_| { + self.commit_if_ok(|snapshot| { + let outer_universe = self.infcx.universe(); + let at = self.at(&self.cause, self.fcx.param_env); let res = if self.use_lub { @@ -138,7 +150,7 @@ fn unify_raw(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> InferResult<'tcx, Ty<'tcx>> { // In the new solver, lazy norm may allow us to shallowly equate // more types, but we emit possibly impossible-to-satisfy obligations. // Filter these cases out to make sure our coercion is more accurate. - match res { + let res = match res { Ok(InferOk { value, obligations }) if self.next_trait_solver() => { let ocx = ObligationCtxt::new(self); ocx.register_obligations(obligations); @@ -149,13 +161,35 @@ fn unify_raw(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> InferResult<'tcx, Ty<'tcx>> { } } res => res, + }; + + // We leak check here mostly because lub operations are + // kind of scuffed around binders. Instead of computing an actual + // lub'd binder we instead: + // - Equate the binders + // - Return the lhs of the lub operation + // + // This may lead to incomplete type inference for the resulting type + // of a `match` or `if .. else`, etc. This is a backwards compat + // hazard for if/when we start handling `lub` more correctly. + // + // In order to actually ensure that equating the binders *does* + // result in equal binders, and that the lhs is actually a supertype + // of the rhs, we must perform a leak check here. + // + // FIXME: Type relations should handle leak checks + // themselves whenever a binder is entered. + if matches!(leak_check, LeakCheck::Yes) { + self.leak_check(outer_universe, Some(snapshot))?; } + + res }) } /// Unify two types (using sub or lub). - fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { - self.unify_raw(a, b) + fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>, leak_check: LeakCheck) -> CoerceResult<'tcx> { + self.unify_raw(a, b, leak_check) .and_then(|InferOk { value: ty, obligations }| success(vec![], ty, obligations)) } @@ -166,8 +200,9 @@ fn unify_and( b: Ty<'tcx>, adjustments: impl IntoIterator>, final_adjustment: Adjust, + leak_check: LeakCheck, ) -> CoerceResult<'tcx> { - self.unify_raw(a, b).and_then(|InferOk { value: ty, obligations }| { + self.unify_raw(a, b, leak_check).and_then(|InferOk { value: ty, obligations }| { success( adjustments .into_iter() @@ -196,7 +231,7 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { ); } else { // Otherwise the only coercion we can do is unification. - return self.unify(a, b); + return self.unify(a, b, LeakCheck::Default); } } @@ -266,7 +301,7 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { } _ => { // Otherwise, just use unification rules. - self.unify(a, b) + self.unify(a, b, LeakCheck::Default) } } } @@ -305,7 +340,7 @@ fn coerce_from_inference_variable(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResu } else { // One unresolved type variable: just apply subtyping, we may be able // to do something useful. - self.unify(a, b) + self.unify(a, b, LeakCheck::Default) } } @@ -332,7 +367,7 @@ fn coerce_to_ref( coerce_mutbls(mutbl, mutbl_b)?; (r_a, ty::TypeAndMut { ty, mutbl }) } - _ => return self.unify(a, b), + _ => return self.unify(a, b, LeakCheck::Default), }; // Look at each step in the `Deref` chain and check if @@ -383,7 +418,7 @@ fn coerce_to_ref( // the `Target` type with the pointee of `b`. This is necessary // to properly account for the differing variances of the pointees // of `&` vs `&mut` references. - match self.unify_raw(autorefd_deref_ty, b) { + match self.unify_raw(autorefd_deref_ty, b, LeakCheck::Default) { Ok(ok) => Some(ok), Err(err) => { if first_error.is_none() { @@ -580,6 +615,7 @@ fn coerce_unsized(&self, source: Ty<'tcx>, target: Ty<'tcx>) -> CoerceResult<'tc target, reborrow.into_iter().flat_map(|(deref, autoref)| [deref, autoref]), Adjust::Pointer(PointerCoercion::Unsize), + LeakCheck::Default, )?; // Create an obligation for `Source: CoerceUnsized`. @@ -794,7 +830,7 @@ fn coerce_to_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { // To complete the reborrow, we need to make sure we can unify the inner types, and if so we // add the adjustments. - self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b)) + self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b), LeakCheck::Default) } fn coerce_from_safe_fn( @@ -805,39 +841,26 @@ fn coerce_from_safe_fn( ) -> CoerceResult<'tcx> { debug_assert!(self.shallow_resolve(b) == b); - self.commit_if_ok(|snapshot| { - let outer_universe = self.infcx.universe(); - - let result = if let ty::FnPtr(_, hdr_b) = b.kind() - && fn_ty_a.safety().is_safe() - && hdr_b.safety.is_unsafe() - { - let unsafe_a = self.tcx.safe_to_unsafe_fn_ty(fn_ty_a); - self.unify_and( - unsafe_a, - b, - adjustment - .map(|kind| Adjustment { kind, target: Ty::new_fn_ptr(self.tcx, fn_ty_a) }), - Adjust::Pointer(PointerCoercion::UnsafeFnPointer), - ) - } else { - let a = Ty::new_fn_ptr(self.tcx, fn_ty_a); - match adjustment { - Some(adjust) => self.unify_and(a, b, [], adjust), - None => self.unify(a, b), - } - }; - - // FIXME(#73154): This is a hack. Currently LUB can generate - // unsolvable constraints. Additionally, it returns `a` - // unconditionally, even when the "LUB" is `b`. In the future, we - // want the coerced type to be the actual supertype of these two, - // but for now, we want to just error to ensure we don't lock - // ourselves into a specific behavior with NLL. - self.leak_check(outer_universe, Some(snapshot))?; - - result - }) + if let ty::FnPtr(_, hdr_b) = b.kind() + && fn_ty_a.safety().is_safe() + && hdr_b.safety.is_unsafe() + { + let unsafe_a = self.tcx.safe_to_unsafe_fn_ty(fn_ty_a); + self.unify_and( + unsafe_a, + b, + adjustment + .map(|kind| Adjustment { kind, target: Ty::new_fn_ptr(self.tcx, fn_ty_a) }), + Adjust::Pointer(PointerCoercion::UnsafeFnPointer), + LeakCheck::Yes, + ) + } else { + let a = Ty::new_fn_ptr(self.tcx, fn_ty_a); + match adjustment { + Some(adjust) => self.unify_and(a, b, [], adjust, LeakCheck::Yes), + None => self.unify(a, b, LeakCheck::Yes), + } + } } fn coerce_from_fn_pointer( @@ -901,7 +924,7 @@ fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { obligations.extend(o2); Ok(InferOk { value, obligations }) } - _ => self.unify(a, b), + _ => self.unify(a, b, LeakCheck::Default), } } @@ -946,9 +969,10 @@ fn coerce_closure_to_fn( b, [], Adjust::Pointer(PointerCoercion::ClosureFnPointer(safety)), + LeakCheck::Default, ) } - _ => self.unify(a, b), + _ => self.unify(a, b, LeakCheck::Default), } } @@ -965,7 +989,7 @@ fn coerce_to_raw_ptr( let (is_ref, mt_a) = match *a.kind() { ty::Ref(_, ty, mutbl) => (true, ty::TypeAndMut { ty, mutbl }), ty::RawPtr(ty, mutbl) => (false, ty::TypeAndMut { ty, mutbl }), - _ => return self.unify(a, b), + _ => return self.unify(a, b, LeakCheck::Default), }; coerce_mutbls(mt_a.mutbl, mutbl_b)?; @@ -980,11 +1004,18 @@ fn coerce_to_raw_ptr( b, [Adjustment { kind: Adjust::Deref(None), target: mt_a.ty }], Adjust::Borrow(AutoBorrow::RawPtr(mutbl_b)), + LeakCheck::Default, ) } else if mt_a.mutbl != mutbl_b { - self.unify_and(a_raw, b, [], Adjust::Pointer(PointerCoercion::MutToConstPointer)) + self.unify_and( + a_raw, + b, + [], + Adjust::Pointer(PointerCoercion::MutToConstPointer), + LeakCheck::Default, + ) } else { - self.unify(a_raw, b) + self.unify(a_raw, b, LeakCheck::Default) } } } @@ -1083,7 +1114,7 @@ pub(crate) fn deref_steps_for_suggestion( // We don't ever need two-phase here since we throw out the result of the coercion. let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true); coerce.autoderef(DUMMY_SP, expr_ty).find_map(|(ty, steps)| { - self.probe(|_| coerce.unify_raw(ty, target)).ok().map(|_| steps) + self.probe(|_| coerce.unify_raw(ty, target, LeakCheck::Default)).ok().map(|_| steps) }) } From 2676c9363c053302492770ecf429bbd8a6a0b172 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Tue, 28 Oct 2025 17:02:51 +0000 Subject: [PATCH 08/26] Properly handle closure<->closure and fndef<->fndef coerce-lubs - leak checking the lub for fndef<->fndef coerce-lubs - start lubbing closure<->closure coerce-lubs and leak check it --- compiler/rustc_hir_typeck/src/coercion.rs | 84 +++++++++++-------- tests/ui/coercion/issue-88097.rs | 3 + tests/ui/coercion/leak_check_fndef_lub.rs | 32 +++++++ .../leak_check_fndef_lub_deadcode_breakage.rs | 29 +++++++ ...k_check_fndef_lub_deadcode_breakage.stderr | 12 +++ .../lub_closures_before_fnptr_coercion.rs | 70 ++++++++++++++++ 6 files changed, 193 insertions(+), 37 deletions(-) create mode 100644 tests/ui/coercion/leak_check_fndef_lub.rs create mode 100644 tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs create mode 100644 tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr create mode 100644 tests/ui/coercion/lub_closures_before_fnptr_coercion.rs diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 9c4c8edd10b7..9aded3f92c76 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1163,8 +1163,9 @@ fn try_find_coercion_lub( exprs.len() ); - // The following check fixes #88097, where the compiler erroneously - // attempted to coerce a closure type to itself via a function pointer. + // Fast Path: don't go through the coercion logic if we're coercing + // a type to itself. This is unfortunately quite perf relevant so + // we do it even though it may mask bugs in the coercion logic. if prev_ty == new_ty { return Ok(prev_ty); } @@ -1193,34 +1194,51 @@ fn try_find_coercion_lub( if is_capturing_closure(prev_ty) || is_capturing_closure(new_ty) { (None, None) } else { - match (prev_ty.kind(), new_ty.kind()) { - (ty::FnDef(..), ty::FnDef(..)) => { - // Don't reify if the function types have a LUB, i.e., they - // are the same function and their parameters have a LUB. - match self.commit_if_ok(|_| { - // We need to eagerly handle nested obligations due to lazy norm. - if self.next_trait_solver() { - let ocx = ObligationCtxt::new(self); - let value = ocx.lub(cause, self.param_env, prev_ty, new_ty)?; - if ocx.try_evaluate_obligations().is_empty() { - Ok(InferOk { - value, - obligations: ocx.into_pending_obligations(), - }) - } else { - Err(TypeError::Mismatch) - } + let lubbed_tys = || { + self.commit_if_ok(|snapshot| { + let outer_universe = self.infcx.universe(); + + // We need to eagerly handle nested obligations due to lazy norm. + let result = if self.next_trait_solver() { + let ocx = ObligationCtxt::new(self); + let value = ocx.lub(cause, self.param_env, prev_ty, new_ty)?; + if ocx.try_evaluate_obligations().is_empty() { + Ok(InferOk { value, obligations: ocx.into_pending_obligations() }) } else { - self.at(cause, self.param_env).lub(prev_ty, new_ty) - } - }) { - // We have a LUB of prev_ty and new_ty, just return it. - Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)), - Err(_) => { - (Some(prev_ty.fn_sig(self.tcx)), Some(new_ty.fn_sig(self.tcx))) + Err(TypeError::Mismatch) } + } else { + self.at(cause, self.param_env).lub(prev_ty, new_ty) + }; + + self.leak_check(outer_universe, Some(snapshot))?; + result + }) + }; + + match (prev_ty.kind(), new_ty.kind()) { + // Don't coerce pairs of fndefs or pairs of closures to fn ptrs + // if they can just be lubbed. + // + // See #88097 or `lub_closures_before_fnptr_coercion.rs` for where + // we would erroneously coerce closures to fnptrs when attempting to + // coerce a closure to itself. + (ty::FnDef(..), ty::FnDef(..)) => match lubbed_tys() { + Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)), + Err(_) => (Some(prev_ty.fn_sig(self.tcx)), Some(new_ty.fn_sig(self.tcx))), + }, + (ty::Closure(_, args_a), ty::Closure(_, args_b)) => match lubbed_tys() { + Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)), + Err(_) => { + let a_sig = self + .tcx + .signature_unclosure(args_a.as_closure().sig(), hir::Safety::Safe); + let b_sig = self + .tcx + .signature_unclosure(args_b.as_closure().sig(), hir::Safety::Safe); + (Some(a_sig), Some(b_sig)) } - } + }, (ty::Closure(_, args), ty::FnDef(..)) => { let b_sig = new_ty.fn_sig(self.tcx); let a_sig = @@ -1233,16 +1251,8 @@ fn try_find_coercion_lub( self.tcx.signature_unclosure(args.as_closure().sig(), a_sig.safety()); (Some(a_sig), Some(b_sig)) } - (ty::Closure(_, args_a), ty::Closure(_, args_b)) => ( - Some( - self.tcx - .signature_unclosure(args_a.as_closure().sig(), hir::Safety::Safe), - ), - Some( - self.tcx - .signature_unclosure(args_b.as_closure().sig(), hir::Safety::Safe), - ), - ), + // ty::FnPtr x ty::FnPtr is fine to just be handled through a normal `unify` + // call using `lub` which is what will happen on the normal path. _ => (None, None), } } diff --git a/tests/ui/coercion/issue-88097.rs b/tests/ui/coercion/issue-88097.rs index f636323d6236..a5804e3b789c 100644 --- a/tests/ui/coercion/issue-88097.rs +++ b/tests/ui/coercion/issue-88097.rs @@ -3,6 +3,9 @@ // behavior has been fixed. //@ check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver fn peculiar() -> impl Fn(u8) -> u8 { return |x| x + 1 diff --git a/tests/ui/coercion/leak_check_fndef_lub.rs b/tests/ui/coercion/leak_check_fndef_lub.rs new file mode 100644 index 000000000000..b13a62db20be --- /dev/null +++ b/tests/ui/coercion/leak_check_fndef_lub.rs @@ -0,0 +1,32 @@ +//@ check-pass + +fn foo() {} + +fn fndef_lub_leak_check() { + macro_rules! lub { + ($lhs:expr, $rhs:expr) => { + if true { $lhs } else { $rhs } + }; + } + + // These don't currently lub but could in theory one day. + // If that happens this test should be adjusted to use + // fn ptrs that can't be lub'd. + let lhs = foo:: fn(&'static (), &'a ())>; + let rhs = foo:: fn(&'a (), &'static ())>; + + // If we leak check then we know we should coerce these + // to `fn()`, if we don't leak check we may try to keep + // them as `FnDef`s which would result in a borrowck + // error. + let lubbed = lub!(lhs, rhs); + + // assert that we coerced lhs/rhs to a fn ptr + is_fnptr(lubbed); +} + +trait FnPtr {} +impl FnPtr for fn() {} +fn is_fnptr(_: T) {} + +fn main() {} diff --git a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs new file mode 100644 index 000000000000..dda6bd6101c4 --- /dev/null +++ b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs @@ -0,0 +1,29 @@ +fn foo() {} + +fn fndef_lub_leak_check() { + macro_rules! lub { + ($lhs:expr, $rhs:expr) => { + if true { $lhs } else { $rhs } + }; + } + + // These don't currently lub but could in theory one day. + // If that happens this test should be adjusted to use + // fn ptrs that can't be lub'd. + let lhs = foo:: fn(&'static (), &'a ())>; + let rhs = foo:: fn(&'a (), &'static ())>; + + loop {} + + // If we leak check then we know we should coerce these + // to `fn()`, if we don't leak check we may try to keep + // them as `FnDef`s which would cause this code to compile + // as borrowck won't emit errors for deadcode. + let lubbed = lub!(lhs, rhs); + + // assert that `lubbed` is a ZST/`FnDef` + unsafe { std::mem::transmute::<_, ()>(lubbed) } + //~^ ERROR: cannot transmute between types of different sizes +} + +fn main() {} diff --git a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr new file mode 100644 index 000000000000..f7336d49e8ca --- /dev/null +++ b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr @@ -0,0 +1,12 @@ +error[E0512]: cannot transmute between types of different sizes, or dependently-sized types + --> $DIR/leak_check_fndef_lub_deadcode_breakage.rs:25:14 + | +LL | unsafe { std::mem::transmute::<_, ()>(lubbed) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: source type: `fn()` (64 bits) + = note: target type: `()` (0 bits) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0512`. diff --git a/tests/ui/coercion/lub_closures_before_fnptr_coercion.rs b/tests/ui/coercion/lub_closures_before_fnptr_coercion.rs new file mode 100644 index 000000000000..b2c421ece570 --- /dev/null +++ b/tests/ui/coercion/lub_closures_before_fnptr_coercion.rs @@ -0,0 +1,70 @@ +//@ check-pass +//@ compile-flags: -Znext-solver + +#![feature(type_alias_impl_trait)] + +// Test that when lubbing two equal closure tys with different +// structural identities (i.e. `PartialEq::eq` on `ty::Ty` would be false) +// we don't coerce-lub to a fnptr. +// +// Most of this test is involved jank to be able to leak the hidden type +// of an opaque with a hidden type of `Closure`. This then allows +// us to substitute `C1` and `C2` for arbitrary types in the parent scope. +// +// See: + +struct WaddupGamers(Option, U); +impl, U> Unpin for WaddupGamers {} +unsafe impl, U> Send for WaddupGamers {} +pub trait Leak { + type Unpin; + type Send; +} +impl Leak for (T,) { + type Unpin = T; + type Send = T; +} +fn define() -> impl Sized { + WaddupGamers(None::, || ()) +} + +fn require_unpin(_: T) {} +fn require_send(_: T) {} +fn mk() -> T { todo!() } + +type NameMe = impl Sized; +type NameMe2 = impl Sized; + +#[define_opaque(NameMe, NameMe2)] +fn leak() +where + T: Leak, Send = NameMe2>, +{ + require_unpin(define:: fn(&'a ())>()); + require_send(define:: fn(&'a ())>()); + + // This is the actual logic for lubbing two closures + // with syntactically different `ty::Ty`s: + + // lhs: Closure fn(&'a1 ())> + let lhs = mk::>(); + // lhs: Closure fn(&'a2 ())> + let rhs = mk::>(); + + macro_rules! lub { + ($lhs:expr, $rhs:expr) => { + if true { $lhs } else { $rhs } + }; + } + + // Lubbed to either: + // - `Closure fn(&'a ())>` + // - `fn(&())` + let lubbed = lub!(lhs, rhs); + + // Use transmute to assert the size of `lubbed` is (), i.e. + // that it is a ZST closure type not a fnptr. + unsafe { std::mem::transmute::<_, ()>(lubbed) }; +} + +fn main() {} From 76bd21ad6644dddfabe8720340d18ad829745d07 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Tue, 28 Oct 2025 17:16:51 +0000 Subject: [PATCH 09/26] account for safe target features in fndef<->closure and fndef<->fndef coerce-lubs --- compiler/rustc_borrowck/src/type_check/mod.rs | 9 +- compiler/rustc_codegen_cranelift/src/base.rs | 2 +- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 2 +- .../src/check_consts/check.rs | 2 +- .../rustc_const_eval/src/interpret/cast.rs | 2 +- compiler/rustc_hir_typeck/src/coercion.rs | 377 ++++++++---------- compiler/rustc_middle/src/ty/adjustment.rs | 5 +- compiler/rustc_middle/src/ty/context.rs | 10 +- compiler/rustc_mir_transform/src/gvn.rs | 2 +- .../src/mentioned_items.rs | 2 +- compiler/rustc_mir_transform/src/validate.rs | 2 +- compiler/rustc_monomorphize/src/collector.rs | 2 +- compiler/rustc_public/src/mir/body.rs | 2 +- .../src/unstable/convert/stable/ty.rs | 4 +- .../clippy_utils/src/qualify_min_const_fn.rs | 2 +- .../build_correct_coerce.main.built.after.mir | 2 +- .../const_prop/reify_fn_ptr.main.GVN.diff | 2 +- tests/mir-opt/const_prop/reify_fn_ptr.rs | 2 +- .../gvn.fn_pointers.GVN.panic-abort.diff | 4 +- .../gvn.fn_pointers.GVN.panic-unwind.diff | 4 +- .../leak_check_fndef_lub_deadcode_breakage.rs | 2 + ...k_check_fndef_lub_deadcode_breakage.stderr | 2 +- .../coercion/lub_coercion_handles_safety.rs | 52 +++ 23 files changed, 266 insertions(+), 229 deletions(-) create mode 100644 tests/ui/coercion/lub_coercion_handles_safety.rs diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 5f86e8646c03..43005fca2984 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1061,7 +1061,10 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { Rvalue::Cast(cast_kind, op, ty) => { match *cast_kind { - CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer, coercion_source) => { + CastKind::PointerCoercion( + PointerCoercion::ReifyFnPointer(target_safety), + coercion_source, + ) => { let is_implicit_coercion = coercion_source == CoercionSource::Implicit; let src_ty = op.ty(self.body, tcx); let mut src_sig = src_ty.fn_sig(tcx); @@ -1078,6 +1081,10 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { src_sig = safe_sig; } + if src_sig.safety().is_safe() && target_safety.is_unsafe() { + src_sig = tcx.safe_to_unsafe_sig(src_sig); + } + // HACK: This shouldn't be necessary... We can remove this when we actually // get binders with where clauses, then elaborate implied bounds into that // binder, and implement a higher-ranked subtyping algorithm that actually diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index e445f9457477..a0bee4e18214 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -689,7 +689,7 @@ fn codegen_stmt<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, cur_block: Block, stmt: lval.write_cvalue(fx, res); } Rvalue::Cast( - CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer, _), + CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer(_), _), ref operand, to_ty, ) => { diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 233f9e787398..de626d04e785 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -405,7 +405,7 @@ pub(crate) fn codegen_rvalue_operand( let lladdr = bx.ptrtoint(llptr, llcast_ty); OperandValue::Immediate(lladdr) } - mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer, _) => { + mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer(_), _) => { match *operand.layout.ty.kind() { ty::FnDef(def_id, args) => { let instance = ty::Instance::resolve_for_fn_ptr( diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 66a2afa0aa7d..78e4066ca910 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -627,7 +627,7 @@ fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { | PointerCoercion::ArrayToPointer | PointerCoercion::UnsafeFnPointer | PointerCoercion::ClosureFnPointer(_) - | PointerCoercion::ReifyFnPointer, + | PointerCoercion::ReifyFnPointer(_), _, ), _, diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs index cf5ee03bedae..3485a5c625ba 100644 --- a/compiler/rustc_const_eval/src/interpret/cast.rs +++ b/compiler/rustc_const_eval/src/interpret/cast.rs @@ -74,7 +74,7 @@ pub fn cast( bug!("{cast_kind:?} casts are for borrowck only, not runtime MIR"); } - CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer, _) => { + CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer(_), _) => { // All reifications must be monomorphic, bail out otherwise. ensure_monomorphic_enough(*self.tcx, src.layout.ty)?; diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 9aded3f92c76..a90702105fbf 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -53,7 +53,7 @@ Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCoercion, }; use rustc_middle::ty::error::TypeError; -use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Span}; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::solve::inspect::{self, InferCtxtProofTreeExt, ProofTreeVisitor}; @@ -291,13 +291,13 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { ty::FnPtr(a_sig_tys, a_hdr) => { // We permit coercion of fn pointers to drop the // unsafe qualifier. - self.coerce_from_fn_pointer(a_sig_tys.with(a_hdr), b) + self.coerce_from_fn_pointer(a, a_sig_tys.with(a_hdr), b) } - ty::Closure(closure_def_id_a, args_a) => { + ty::Closure(..) => { // Non-capturing closures are coercible to // function pointers or unsafe function pointers. // It cannot convert closures that require unsafe. - self.coerce_closure_to_fn(a, closure_def_id_a, args_a, b) + self.coerce_closure_to_fn(a, b) } _ => { // Otherwise, just use unification rules. @@ -833,45 +833,23 @@ fn coerce_to_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b), LeakCheck::Default) } - fn coerce_from_safe_fn( - &self, - fn_ty_a: ty::PolyFnSig<'tcx>, - b: Ty<'tcx>, - adjustment: Option, - ) -> CoerceResult<'tcx> { - debug_assert!(self.shallow_resolve(b) == b); - - if let ty::FnPtr(_, hdr_b) = b.kind() - && fn_ty_a.safety().is_safe() - && hdr_b.safety.is_unsafe() - { - let unsafe_a = self.tcx.safe_to_unsafe_fn_ty(fn_ty_a); - self.unify_and( - unsafe_a, - b, - adjustment - .map(|kind| Adjustment { kind, target: Ty::new_fn_ptr(self.tcx, fn_ty_a) }), - Adjust::Pointer(PointerCoercion::UnsafeFnPointer), - LeakCheck::Yes, - ) - } else { - let a = Ty::new_fn_ptr(self.tcx, fn_ty_a); - match adjustment { - Some(adjust) => self.unify_and(a, b, [], adjust, LeakCheck::Yes), - None => self.unify(a, b, LeakCheck::Yes), - } - } - } - fn coerce_from_fn_pointer( &self, - fn_ty_a: ty::PolyFnSig<'tcx>, + a: Ty<'tcx>, + a_sig: ty::PolyFnSig<'tcx>, b: Ty<'tcx>, ) -> CoerceResult<'tcx> { - debug!(?fn_ty_a, ?b, "coerce_from_fn_pointer"); + debug!(?a_sig, ?b, "coerce_from_fn_pointer"); debug_assert!(self.shallow_resolve(b) == b); - self.coerce_from_safe_fn(fn_ty_a, b, None) + match b.kind() { + ty::FnPtr(_, b_hdr) if a_sig.safety().is_safe() && b_hdr.safety.is_unsafe() => { + let a = self.tcx.safe_to_unsafe_fn_ty(a_sig); + let adjust = Adjust::Pointer(PointerCoercion::UnsafeFnPointer); + self.unify_and(a, b, [], adjust, LeakCheck::Yes) + } + _ => self.unify(a, b, LeakCheck::Yes), + } } fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { @@ -881,45 +859,17 @@ fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { match b.kind() { ty::FnPtr(_, b_hdr) => { - let mut a_sig = a.fn_sig(self.tcx); - if let ty::FnDef(def_id, _) = *a.kind() { - // Intrinsics are not coercible to function pointers - if self.tcx.intrinsic(def_id).is_some() { - return Err(TypeError::IntrinsicCast); - } - - let fn_attrs = self.tcx.codegen_fn_attrs(def_id); - if matches!(fn_attrs.inline, InlineAttr::Force { .. }) { - return Err(TypeError::ForceInlineCast); - } - - if b_hdr.safety.is_safe() - && self.tcx.codegen_fn_attrs(def_id).safe_target_features - { - // Allow the coercion if the current function has all the features that would be - // needed to call the coercee safely. - if let Some(safe_sig) = self.tcx.adjust_target_feature_sig( - def_id, - a_sig, - self.fcx.body_id.into(), - ) { - a_sig = safe_sig; - } else { - return Err(TypeError::TargetFeatureCast(def_id)); - } - } - } + let a_sig = self.sig_for_fn_def_coercion(a, Some(b_hdr.safety))?; // FIXME: we shouldn't be normalizing here as coercion is inside of // a probe. This can probably cause ICEs. let InferOk { value: a_sig, mut obligations } = self.at(&self.cause, self.param_env).normalize(a_sig); + let a = Ty::new_fn_ptr(self.tcx, a_sig); - let InferOk { value, obligations: o2 } = self.coerce_from_safe_fn( - a_sig, - b, - Some(Adjust::Pointer(PointerCoercion::ReifyFnPointer)), - )?; + let adjust = Adjust::Pointer(PointerCoercion::ReifyFnPointer(b_hdr.safety)); + let InferOk { value, obligations: o2 } = + self.unify_and(a, b, [], adjust, LeakCheck::Yes)?; obligations.extend(o2); Ok(InferOk { value, obligations }) @@ -930,47 +880,20 @@ fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { /// Attempts to coerce from a closure to a function pointer. Fails /// if the closure has any upvars. - fn coerce_closure_to_fn( - &self, - a: Ty<'tcx>, - closure_def_id_a: DefId, - args_a: GenericArgsRef<'tcx>, - b: Ty<'tcx>, - ) -> CoerceResult<'tcx> { + fn coerce_closure_to_fn(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { debug_assert!(self.shallow_resolve(a) == a); debug_assert!(self.shallow_resolve(b) == b); match b.kind() { - // At this point we haven't done capture analysis, which means - // that the ClosureArgs just contains an inference variable instead - // of tuple of captured types. - // - // All we care here is if any variable is being captured and not the exact paths, - // so we check `upvars_mentioned` for root variables being captured. - ty::FnPtr(_, hdr) - if self - .tcx - .upvars_mentioned(closure_def_id_a.expect_local()) - .is_none_or(|u| u.is_empty()) => - { - // We coerce the closure, which has fn type - // `extern "rust-call" fn((arg0,arg1,...)) -> _` - // to - // `fn(arg0,arg1,...) -> _` - // or - // `unsafe fn(arg0,arg1,...) -> _` - let closure_sig = args_a.as_closure().sig(); + ty::FnPtr(_, hdr) => { let safety = hdr.safety; - let pointer_ty = - Ty::new_fn_ptr(self.tcx, self.tcx.signature_unclosure(closure_sig, safety)); + let terr = TypeError::Sorts(ty::error::ExpectedFound::new(a, b)); + let closure_sig = self.sig_for_closure_coercion(a, Some(hdr.safety), terr)?; + let pointer_ty = Ty::new_fn_ptr(self.tcx, closure_sig); debug!("coerce_closure_to_fn(a={:?}, b={:?}, pty={:?})", a, b, pointer_ty); - self.unify_and( - pointer_ty, - b, - [], - Adjust::Pointer(PointerCoercion::ClosureFnPointer(safety)), - LeakCheck::Default, - ) + + let adjust = Adjust::Pointer(PointerCoercion::ClosureFnPointer(safety)); + self.unify_and(pointer_ty, b, [], adjust, LeakCheck::Default) } _ => self.unify(a, b, LeakCheck::Default), } @@ -1137,6 +1060,94 @@ pub(crate) fn deref_once_mutably_for_diagnostic(&self, expr_ty: Ty<'tcx>) -> Opt }) } + #[instrument(level = "debug", skip(self), ret)] + fn sig_for_coerce_lub( + &self, + ty: Ty<'tcx>, + closure_upvars_terr: TypeError<'tcx>, + ) -> Result, TypeError<'tcx>> { + match ty.kind() { + ty::FnDef(..) => self.sig_for_fn_def_coercion(ty, None), + ty::Closure(..) => self.sig_for_closure_coercion(ty, None, closure_upvars_terr), + _ => unreachable!("`sig_for_fn_def_closure_coerce_lub` called with wrong ty: {:?}", ty), + } + } + + fn sig_for_fn_def_coercion( + &self, + fndef: Ty<'tcx>, + expected_safety: Option, + ) -> Result, TypeError<'tcx>> { + let tcx = self.tcx; + + let &ty::FnDef(def_id, _) = fndef.kind() else { + unreachable!("`sig_for_fn_def_coercion` called with non-fndef: {:?}", fndef); + }; + + // Intrinsics are not coercible to function pointers + if tcx.intrinsic(def_id).is_some() { + return Err(TypeError::IntrinsicCast); + } + + let fn_attrs = tcx.codegen_fn_attrs(def_id); + if matches!(fn_attrs.inline, InlineAttr::Force { .. }) { + return Err(TypeError::ForceInlineCast); + } + + let sig = fndef.fn_sig(tcx); + let sig = if fn_attrs.safe_target_features { + // Allow the coercion if the current function has all the features that would be + // needed to call the coercee safely. + match tcx.adjust_target_feature_sig(def_id, sig, self.body_id.into()) { + Some(adjusted_sig) => adjusted_sig, + None if matches!(expected_safety, Some(hir::Safety::Safe)) => { + return Err(TypeError::TargetFeatureCast(def_id)); + } + None => sig, + } + } else { + sig + }; + + if sig.safety().is_safe() && matches!(expected_safety, Some(hir::Safety::Unsafe)) { + Ok(tcx.safe_to_unsafe_sig(sig)) + } else { + Ok(sig) + } + } + + fn sig_for_closure_coercion( + &self, + closure: Ty<'tcx>, + expected_safety: Option, + closure_upvars_terr: TypeError<'tcx>, + ) -> Result, TypeError<'tcx>> { + let tcx = self.tcx; + + let ty::Closure(closure_def, closure_args) = closure.kind() else { + unreachable!("`sig_for_closure_coercion` called with non closure ty: {:?}", closure); + }; + + // At this point we haven't done capture analysis, which means + // that the ClosureArgs just contains an inference variable instead + // of tuple of captured types. + // + // All we care here is if any variable is being captured and not the exact paths, + // so we check `upvars_mentioned` for root variables being captured. + if !tcx.upvars_mentioned(closure_def.expect_local()).is_none_or(|u| u.is_empty()) { + return Err(closure_upvars_terr); + } + + // We coerce the closure, which has fn type + // `extern "rust-call" fn((arg0,arg1,...)) -> _` + // to + // `fn(arg0,arg1,...) -> _` + // or + // `unsafe fn(arg0,arg1,...) -> _` + let closure_sig = closure_args.as_closure().sig(); + Ok(tcx.signature_unclosure(closure_sig, expected_safety.unwrap_or(hir::Safety::Safe))) + } + /// Given some expressions, their known unified type and another expression, /// tries to unify the types, potentially inserting coercions on any of the /// provided expressions and returns their LUB (aka "common supertype"). @@ -1170,94 +1181,64 @@ fn try_find_coercion_lub( return Ok(prev_ty); } - let is_force_inline = |ty: Ty<'tcx>| { - if let ty::FnDef(did, _) = ty.kind() { - matches!(self.tcx.codegen_fn_attrs(did).inline, InlineAttr::Force { .. }) - } else { - false - } - }; - if is_force_inline(prev_ty) || is_force_inline(new_ty) { - return Err(TypeError::ForceInlineCast); - } + let terr = TypeError::Sorts(ty::error::ExpectedFound::new(prev_ty, new_ty)); + let opt_sigs = match (prev_ty.kind(), new_ty.kind()) { + // Don't coerce pairs of fndefs or pairs of closures to fn ptrs + // if they can just be lubbed. + // + // See #88097 or `lub_closures_before_fnptr_coercion.rs` for where + // we would erroneously coerce closures to fnptrs when attempting to + // coerce a closure to itself. + (ty::FnDef(..), ty::FnDef(..)) | (ty::Closure(..), ty::Closure(..)) => { + let lubbed_ty = self.commit_if_ok(|snapshot| { + let outer_universe = self.infcx.universe(); - // Special-case that coercion alone cannot handle: - // Function items or non-capturing closures of differing IDs or GenericArgs. - let (a_sig, b_sig) = { - let is_capturing_closure = |ty: Ty<'tcx>| { - if let &ty::Closure(closure_def_id, _args) = ty.kind() { - self.tcx.upvars_mentioned(closure_def_id.expect_local()).is_some() - } else { - false - } - }; - if is_capturing_closure(prev_ty) || is_capturing_closure(new_ty) { - (None, None) - } else { - let lubbed_tys = || { - self.commit_if_ok(|snapshot| { - let outer_universe = self.infcx.universe(); - - // We need to eagerly handle nested obligations due to lazy norm. - let result = if self.next_trait_solver() { - let ocx = ObligationCtxt::new(self); - let value = ocx.lub(cause, self.param_env, prev_ty, new_ty)?; - if ocx.try_evaluate_obligations().is_empty() { - Ok(InferOk { value, obligations: ocx.into_pending_obligations() }) - } else { - Err(TypeError::Mismatch) - } + // We need to eagerly handle nested obligations due to lazy norm. + let result = if self.next_trait_solver() { + let ocx = ObligationCtxt::new(self); + let value = ocx.lub(cause, self.param_env, prev_ty, new_ty)?; + if ocx.try_evaluate_obligations().is_empty() { + Ok(InferOk { value, obligations: ocx.into_pending_obligations() }) } else { - self.at(cause, self.param_env).lub(prev_ty, new_ty) - }; - - self.leak_check(outer_universe, Some(snapshot))?; - result - }) - }; - - match (prev_ty.kind(), new_ty.kind()) { - // Don't coerce pairs of fndefs or pairs of closures to fn ptrs - // if they can just be lubbed. - // - // See #88097 or `lub_closures_before_fnptr_coercion.rs` for where - // we would erroneously coerce closures to fnptrs when attempting to - // coerce a closure to itself. - (ty::FnDef(..), ty::FnDef(..)) => match lubbed_tys() { - Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)), - Err(_) => (Some(prev_ty.fn_sig(self.tcx)), Some(new_ty.fn_sig(self.tcx))), - }, - (ty::Closure(_, args_a), ty::Closure(_, args_b)) => match lubbed_tys() { - Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)), - Err(_) => { - let a_sig = self - .tcx - .signature_unclosure(args_a.as_closure().sig(), hir::Safety::Safe); - let b_sig = self - .tcx - .signature_unclosure(args_b.as_closure().sig(), hir::Safety::Safe); - (Some(a_sig), Some(b_sig)) + Err(TypeError::Mismatch) } - }, - (ty::Closure(_, args), ty::FnDef(..)) => { - let b_sig = new_ty.fn_sig(self.tcx); - let a_sig = - self.tcx.signature_unclosure(args.as_closure().sig(), b_sig.safety()); - (Some(a_sig), Some(b_sig)) + } else { + self.at(cause, self.param_env).lub(prev_ty, new_ty) + }; + + self.leak_check(outer_universe, Some(snapshot))?; + result + }); + + match lubbed_ty { + Ok(ok) => return Ok(self.register_infer_ok_obligations(ok)), + Err(_) => { + let a_sig = self.sig_for_coerce_lub(prev_ty, terr)?; + let b_sig = self.sig_for_coerce_lub(new_ty, terr)?; + Some((a_sig, b_sig)) } - (ty::FnDef(..), ty::Closure(_, args)) => { - let a_sig = prev_ty.fn_sig(self.tcx); - let b_sig = - self.tcx.signature_unclosure(args.as_closure().sig(), a_sig.safety()); - (Some(a_sig), Some(b_sig)) - } - // ty::FnPtr x ty::FnPtr is fine to just be handled through a normal `unify` - // call using `lub` which is what will happen on the normal path. - _ => (None, None), } } + + (ty::Closure(..), ty::FnDef(..)) | (ty::FnDef(..), ty::Closure(..)) => { + let a_sig = self.sig_for_coerce_lub(prev_ty, terr)?; + let b_sig = self.sig_for_coerce_lub(new_ty, terr)?; + Some((a_sig, b_sig)) + } + // ty::FnPtr x ty::FnPtr is fine to just be handled through a normal `unify` + // call using `lub` which is what will happen on the normal path. + (ty::FnPtr(..), ty::FnPtr(..)) => None, + _ => None, }; - if let (Some(a_sig), Some(b_sig)) = (a_sig, b_sig) { + + if let Some((mut a_sig, mut b_sig)) = opt_sigs { + // Allow coercing safe sigs to unsafe sigs + if a_sig.safety().is_safe() && b_sig.safety().is_unsafe() { + a_sig = self.tcx.safe_to_unsafe_sig(a_sig); + } else if b_sig.safety().is_safe() && a_sig.safety().is_unsafe() { + b_sig = self.tcx.safe_to_unsafe_sig(b_sig); + }; + // The signature must match. let (a_sig, b_sig) = self.normalize(new.span, (a_sig, b_sig)); let sig = self @@ -1268,29 +1249,13 @@ fn try_find_coercion_lub( // Reify both sides and return the reified fn pointer type. let fn_ptr = Ty::new_fn_ptr(self.tcx, sig); let prev_adjustment = match prev_ty.kind() { - ty::Closure(..) => { - Adjust::Pointer(PointerCoercion::ClosureFnPointer(a_sig.safety())) - } - ty::FnDef(def_id, ..) => { - // Intrinsics are not coercible to function pointers - if self.tcx.intrinsic(def_id).is_some() { - return Err(TypeError::IntrinsicCast); - } - Adjust::Pointer(PointerCoercion::ReifyFnPointer) - } + ty::Closure(..) => Adjust::Pointer(PointerCoercion::ClosureFnPointer(sig.safety())), + ty::FnDef(..) => Adjust::Pointer(PointerCoercion::ReifyFnPointer(sig.safety())), _ => span_bug!(cause.span, "should not try to coerce a {prev_ty} to a fn pointer"), }; let next_adjustment = match new_ty.kind() { - ty::Closure(..) => { - Adjust::Pointer(PointerCoercion::ClosureFnPointer(b_sig.safety())) - } - ty::FnDef(def_id, ..) => { - // Intrinsics are not coercible to function pointers - if self.tcx.intrinsic(def_id).is_some() { - return Err(TypeError::IntrinsicCast); - } - Adjust::Pointer(PointerCoercion::ReifyFnPointer) - } + ty::Closure(..) => Adjust::Pointer(PointerCoercion::ClosureFnPointer(sig.safety())), + ty::FnDef(..) => Adjust::Pointer(PointerCoercion::ReifyFnPointer(sig.safety())), _ => span_bug!(new.span, "should not try to coerce a {new_ty} to a fn pointer"), }; for expr in exprs.iter().map(|e| e.as_coercion_site()) { diff --git a/compiler/rustc_middle/src/ty/adjustment.rs b/compiler/rustc_middle/src/ty/adjustment.rs index 2920c9cb42ab..c806366b518a 100644 --- a/compiler/rustc_middle/src/ty/adjustment.rs +++ b/compiler/rustc_middle/src/ty/adjustment.rs @@ -9,8 +9,9 @@ #[derive(Clone, Copy, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] pub enum PointerCoercion { - /// Go from a fn-item type to a fn-pointer type. - ReifyFnPointer, + /// Go from a fn-item type to a fn pointer or an unsafe fn pointer. + /// It cannot convert an unsafe fn-item to a safe fn pointer. + ReifyFnPointer(hir::Safety), /// Go from a safe fn pointer to an unsafe fn pointer. UnsafeFnPointer, diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 0cd36d5e971d..baf4d364714a 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2837,7 +2837,7 @@ impl<'tcx> TyCtxt<'tcx> { ); impl<'tcx> TyCtxt<'tcx> { - /// Given a `fn` type, returns an equivalent `unsafe fn` type; + /// Given a `fn` sig, returns an equivalent `unsafe fn` type; /// that is, a `fn` type that is equivalent in every way for being /// unsafe. pub fn safe_to_unsafe_fn_ty(self, sig: PolyFnSig<'tcx>) -> Ty<'tcx> { @@ -2845,6 +2845,14 @@ pub fn safe_to_unsafe_fn_ty(self, sig: PolyFnSig<'tcx>) -> Ty<'tcx> { Ty::new_fn_ptr(self, sig.map_bound(|sig| ty::FnSig { safety: hir::Safety::Unsafe, ..sig })) } + /// Given a `fn` sig, returns an equivalent `unsafe fn` sig; + /// that is, a `fn` sig that is equivalent in every way for being + /// unsafe. + pub fn safe_to_unsafe_sig(self, sig: PolyFnSig<'tcx>) -> PolyFnSig<'tcx> { + assert!(sig.safety().is_safe()); + sig.map_bound(|sig| ty::FnSig { safety: hir::Safety::Unsafe, ..sig }) + } + /// Given the def_id of a Trait `trait_def_id` and the name of an associated item `assoc_name` /// returns true if the `trait_def_id` defines an associated item of name `assoc_name`. pub fn trait_may_define_assoc_item(self, trait_def_id: DefId, assoc_name: Ident) -> bool { diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index d9c26faaf445..ebfeba5ad225 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -1518,7 +1518,7 @@ fn simplify_cast( return Some(value); } - if let CastKind::PointerCoercion(ReifyFnPointer | ClosureFnPointer(_), _) = kind { + if let CastKind::PointerCoercion(ReifyFnPointer(_) | ClosureFnPointer(_), _) = kind { // Each reification of a generic fn may get a different pointer. // Do not try to merge them. return Some(self.new_opaque(to)); diff --git a/compiler/rustc_mir_transform/src/mentioned_items.rs b/compiler/rustc_mir_transform/src/mentioned_items.rs index f011d394f616..a9f2c32171c3 100644 --- a/compiler/rustc_mir_transform/src/mentioned_items.rs +++ b/compiler/rustc_mir_transform/src/mentioned_items.rs @@ -109,7 +109,7 @@ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) { } // And finally, function pointer reification casts. mir::Rvalue::Cast( - mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer, _), + mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer(_), _), ref operand, _, ) => { diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 91617be085c3..cf8247c12abd 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -1272,7 +1272,7 @@ macro_rules! check_kinds { match kind { // FIXME: Add Checks for these CastKind::PointerWithExposedProvenance | CastKind::PointerExposeProvenance => {} - CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer, _) => { + CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer(_), _) => { // FIXME: check signature compatibility. check_kinds!( op_ty, diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index f33f22460467..948f965ed7ad 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -765,7 +765,7 @@ fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) { } } mir::Rvalue::Cast( - mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer, _), + mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer(_), _), ref operand, _, ) => { diff --git a/compiler/rustc_public/src/mir/body.rs b/compiler/rustc_public/src/mir/body.rs index fde4d40bea10..03f289f30f42 100644 --- a/compiler/rustc_public/src/mir/body.rs +++ b/compiler/rustc_public/src/mir/body.rs @@ -978,7 +978,7 @@ pub enum Safety { #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize)] pub enum PointerCoercion { /// Go from a fn-item type to a fn-pointer type. - ReifyFnPointer, + ReifyFnPointer(Safety), /// Go from a safe fn pointer to an unsafe fn pointer. UnsafeFnPointer, diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index 36fc5724d51a..ca8234280be8 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -130,7 +130,9 @@ fn stable<'cx>( ) -> Self::T { use rustc_middle::ty::adjustment::PointerCoercion; match self { - PointerCoercion::ReifyFnPointer => crate::mir::PointerCoercion::ReifyFnPointer, + PointerCoercion::ReifyFnPointer(safety) => { + crate::mir::PointerCoercion::ReifyFnPointer(safety.stable(tables, cx)) + } PointerCoercion::UnsafeFnPointer => crate::mir::PointerCoercion::UnsafeFnPointer, PointerCoercion::ClosureFnPointer(safety) => { crate::mir::PointerCoercion::ClosureFnPointer(safety.stable(tables, cx)) diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 1fc8e86f3193..462cc644d4be 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -150,7 +150,7 @@ fn check_rvalue<'tcx>( CastKind::PointerCoercion( PointerCoercion::UnsafeFnPointer | PointerCoercion::ClosureFnPointer(_) - | PointerCoercion::ReifyFnPointer, + | PointerCoercion::ReifyFnPointer(_), _, ), _, diff --git a/tests/mir-opt/build_correct_coerce.main.built.after.mir b/tests/mir-opt/build_correct_coerce.main.built.after.mir index 583a5ecd2270..1f2a271b6294 100644 --- a/tests/mir-opt/build_correct_coerce.main.built.after.mir +++ b/tests/mir-opt/build_correct_coerce.main.built.after.mir @@ -9,7 +9,7 @@ fn main() -> () { bb0: { StorageLive(_1); - _1 = foo as for<'a> fn(&'a (), &'a ()) (PointerCoercion(ReifyFnPointer, AsCast)); + _1 = foo as for<'a> fn(&'a (), &'a ()) (PointerCoercion(ReifyFnPointer(Safe), AsCast)); FakeRead(ForLet(None), _1); _0 = const (); StorageDead(_1); diff --git a/tests/mir-opt/const_prop/reify_fn_ptr.main.GVN.diff b/tests/mir-opt/const_prop/reify_fn_ptr.main.GVN.diff index 50a17326c2aa..7919c816b561 100644 --- a/tests/mir-opt/const_prop/reify_fn_ptr.main.GVN.diff +++ b/tests/mir-opt/const_prop/reify_fn_ptr.main.GVN.diff @@ -13,7 +13,7 @@ StorageLive(_1); StorageLive(_2); StorageLive(_3); - _3 = main as fn() (PointerCoercion(ReifyFnPointer, AsCast)); + _3 = main as fn() (PointerCoercion(ReifyFnPointer(Safe), AsCast)); _2 = move _3 as usize (PointerExposeProvenance); StorageDead(_3); _1 = move _2 as *const fn() (PointerWithExposedProvenance); diff --git a/tests/mir-opt/const_prop/reify_fn_ptr.rs b/tests/mir-opt/const_prop/reify_fn_ptr.rs index d56f21e586aa..92b9a923a424 100644 --- a/tests/mir-opt/const_prop/reify_fn_ptr.rs +++ b/tests/mir-opt/const_prop/reify_fn_ptr.rs @@ -3,7 +3,7 @@ fn main() { // CHECK-LABEL: fn main( - // CHECK: [[ptr:_.*]] = main as fn() (PointerCoercion(ReifyFnPointer, AsCast)); + // CHECK: [[ptr:_.*]] = main as fn() (PointerCoercion(ReifyFnPointer(Safe), AsCast)); // CHECK: [[addr:_.*]] = move [[ptr]] as usize (PointerExposeProvenance); // CHECK: [[back:_.*]] = move [[addr]] as *const fn() (PointerWithExposedProvenance); let _ = main as usize as *const fn(); diff --git a/tests/mir-opt/gvn.fn_pointers.GVN.panic-abort.diff b/tests/mir-opt/gvn.fn_pointers.GVN.panic-abort.diff index f3f631956374..90920dd0be8f 100644 --- a/tests/mir-opt/gvn.fn_pointers.GVN.panic-abort.diff +++ b/tests/mir-opt/gvn.fn_pointers.GVN.panic-abort.diff @@ -37,7 +37,7 @@ bb0: { - StorageLive(_1); + nop; - _1 = identity:: as fn(u8) -> u8 (PointerCoercion(ReifyFnPointer, AsCast)); + _1 = identity:: as fn(u8) -> u8 (PointerCoercion(ReifyFnPointer(Safe), AsCast)); StorageLive(_2); StorageLive(_3); _3 = copy _1; @@ -50,7 +50,7 @@ StorageDead(_2); - StorageLive(_4); + nop; - _4 = identity:: as fn(u8) -> u8 (PointerCoercion(ReifyFnPointer, AsCast)); + _4 = identity:: as fn(u8) -> u8 (PointerCoercion(ReifyFnPointer(Safe), AsCast)); StorageLive(_5); StorageLive(_6); _6 = copy _4; diff --git a/tests/mir-opt/gvn.fn_pointers.GVN.panic-unwind.diff b/tests/mir-opt/gvn.fn_pointers.GVN.panic-unwind.diff index 029e736a9795..0aca8e508f5c 100644 --- a/tests/mir-opt/gvn.fn_pointers.GVN.panic-unwind.diff +++ b/tests/mir-opt/gvn.fn_pointers.GVN.panic-unwind.diff @@ -37,7 +37,7 @@ bb0: { - StorageLive(_1); + nop; - _1 = identity:: as fn(u8) -> u8 (PointerCoercion(ReifyFnPointer, AsCast)); + _1 = identity:: as fn(u8) -> u8 (PointerCoercion(ReifyFnPointer(Safe), AsCast)); StorageLive(_2); StorageLive(_3); _3 = copy _1; @@ -50,7 +50,7 @@ StorageDead(_2); - StorageLive(_4); + nop; - _4 = identity:: as fn(u8) -> u8 (PointerCoercion(ReifyFnPointer, AsCast)); + _4 = identity:: as fn(u8) -> u8 (PointerCoercion(ReifyFnPointer(Safe), AsCast)); StorageLive(_5); StorageLive(_6); _6 = copy _4; diff --git a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs index dda6bd6101c4..e2743bf39ab6 100644 --- a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs +++ b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs @@ -1,3 +1,5 @@ +//@ normalize-stderr: "32 bits" -> "64 bits" + fn foo() {} fn fndef_lub_leak_check() { diff --git a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr index f7336d49e8ca..60e63ee93269 100644 --- a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr +++ b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr @@ -1,5 +1,5 @@ error[E0512]: cannot transmute between types of different sizes, or dependently-sized types - --> $DIR/leak_check_fndef_lub_deadcode_breakage.rs:25:14 + --> $DIR/leak_check_fndef_lub_deadcode_breakage.rs:27:14 | LL | unsafe { std::mem::transmute::<_, ()>(lubbed) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/coercion/lub_coercion_handles_safety.rs b/tests/ui/coercion/lub_coercion_handles_safety.rs new file mode 100644 index 000000000000..e44874f46cec --- /dev/null +++ b/tests/ui/coercion/lub_coercion_handles_safety.rs @@ -0,0 +1,52 @@ +//@ check-pass + +//@ only-x86_64 +// because target features + +macro_rules! lub { + ($lhs:expr, $rhs:expr) => { + if true { $lhs } else { $rhs } + }; +} + +fn safety_lub() { + unsafe fn lhs() {} + fn rhs() {} + + // We have two different fn defs, the only valid lub here + // is to go to fnptrs. However, in order to go to fnptrs + // `rhs` must coerce from a *safe* function to an *unsafe* + // one. + let lubbed = lub!(lhs, rhs); + is_unsafe_fnptr(lubbed); +} + +#[target_feature(enable = "sse2")] +fn target_feature_aware_safety_lub() { + #[target_feature(enable = "sse2")] + fn lhs() {} + fn rhs() {} + unsafe fn rhs_unsafe() {} + + // We have two different fn defs, the only valid lub here + // is to go to fnptrs. However, in order to go to fnptrs + // `lhs` must coerce from an unsafe fn to a safe one due + // to the correct target features being enabled + let lubbed = lub!(lhs, rhs); + is_fnptr(lubbed); + + // Similar case here except we must recognise that rhs + // is an unsafe fn so lhs must be an unsafe fn even though + // it *could* be safe + let lubbed = lub!(lhs, rhs_unsafe); + is_unsafe_fnptr(lubbed); +} + +trait FnPtr {} +impl FnPtr for fn() {} +fn is_fnptr(_: T) {} +trait UnsafeFnPtr {} +impl UnsafeFnPtr for unsafe fn() {} +fn is_unsafe_fnptr(_: T) {} + +fn main() {} From 24a3f58adef9bd1e3222da49bdaacf3d434dca90 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Thu, 30 Oct 2025 16:16:14 +0000 Subject: [PATCH 10/26] remove redundant `lub` --- compiler/rustc_hir_typeck/src/coercion.rs | 37 +++++++++-------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index a90702105fbf..6daeb917014c 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1298,30 +1298,21 @@ fn try_find_coercion_lub( } } - match self.commit_if_ok(|_| coerce.coerce(prev_ty, new_ty)) { - Err(_) => { - // Avoid giving strange errors on failed attempts. - if let Some(e) = first_error { - Err(e) - } else { - Err(self - .commit_if_ok(|_| self.at(cause, self.param_env).lub(prev_ty, new_ty)) - .unwrap_err()) - } - } - Ok(ok) => { - let (adjustments, target) = self.register_infer_ok_obligations(ok); - for expr in exprs { - let expr = expr.as_coercion_site(); - self.apply_adjustments(expr, adjustments.clone()); - } - debug!( - "coercion::try_find_coercion_lub: was able to coerce previous type {:?} to new type {:?} ({:?})", - prev_ty, new_ty, target - ); - Ok(target) - } + let ok = self + .commit_if_ok(|_| coerce.coerce(prev_ty, new_ty)) + // Avoid giving strange errors on failed attempts. + .map_err(|e| first_error.unwrap_or(e))?; + + let (adjustments, target) = self.register_infer_ok_obligations(ok); + for expr in exprs { + let expr = expr.as_coercion_site(); + self.apply_adjustments(expr, adjustments.clone()); } + debug!( + "coercion::try_find_coercion_lub: was able to coerce previous type {:?} to new type {:?} ({:?})", + prev_ty, new_ty, target + ); + Ok(target) } } From 2f95ecfdd63c2ec27067d0cf3e8eb1155b872af7 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Wed, 3 Dec 2025 14:53:03 +0000 Subject: [PATCH 11/26] reviews --- compiler/rustc_hir_typeck/src/coercion.rs | 89 +++++++++++-------- .../hr_alias_normalization_leaking_vars.rs | 2 + ...hr_alias_normalization_leaking_vars.stderr | 12 +-- tests/ui/coercion/leak_check_fndef_lub.rs | 4 +- .../leak_check_fndef_lub_deadcode_breakage.rs | 4 +- ...k_check_fndef_lub_deadcode_breakage.stderr | 2 +- .../lub_closures_before_fnptr_coercion.rs | 70 --------------- 7 files changed, 61 insertions(+), 122 deletions(-) delete mode 100644 tests/ui/coercion/lub_closures_before_fnptr_coercion.rs diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 6daeb917014c..99dbb1958cf6 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -113,9 +113,15 @@ fn success<'tcx>( Ok(InferOk { value: (adj, target), obligations }) } -enum LeakCheck { +/// Whether to force a leak check to occur in `Coerce::unify_raw`. +/// Note that leak checks may still occur evn with `ForceLeakCheck::No`. +/// +/// FIXME: We may want to change type relations to always leak-check +/// after exiting a binder, at which point we will always do so and +/// no longer need to handle this explicitly +enum ForceLeakCheck { Yes, - Default, + No, } impl<'f, 'tcx> Coerce<'f, 'tcx> { @@ -132,7 +138,7 @@ fn unify_raw( &self, a: Ty<'tcx>, b: Ty<'tcx>, - leak_check: LeakCheck, + leak_check: ForceLeakCheck, ) -> InferResult<'tcx, Ty<'tcx>> { debug!("unify(a: {:?}, b: {:?}, use_lub: {})", a, b, self.use_lub); self.commit_if_ok(|snapshot| { @@ -176,10 +182,7 @@ fn unify_raw( // In order to actually ensure that equating the binders *does* // result in equal binders, and that the lhs is actually a supertype // of the rhs, we must perform a leak check here. - // - // FIXME: Type relations should handle leak checks - // themselves whenever a binder is entered. - if matches!(leak_check, LeakCheck::Yes) { + if matches!(leak_check, ForceLeakCheck::Yes) { self.leak_check(outer_universe, Some(snapshot))?; } @@ -188,7 +191,7 @@ fn unify_raw( } /// Unify two types (using sub or lub). - fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>, leak_check: LeakCheck) -> CoerceResult<'tcx> { + fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>, leak_check: ForceLeakCheck) -> CoerceResult<'tcx> { self.unify_raw(a, b, leak_check) .and_then(|InferOk { value: ty, obligations }| success(vec![], ty, obligations)) } @@ -200,7 +203,7 @@ fn unify_and( b: Ty<'tcx>, adjustments: impl IntoIterator>, final_adjustment: Adjust, - leak_check: LeakCheck, + leak_check: ForceLeakCheck, ) -> CoerceResult<'tcx> { self.unify_raw(a, b, leak_check).and_then(|InferOk { value: ty, obligations }| { success( @@ -231,7 +234,7 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { ); } else { // Otherwise the only coercion we can do is unification. - return self.unify(a, b, LeakCheck::Default); + return self.unify(a, b, ForceLeakCheck::No); } } @@ -265,7 +268,7 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { return self.coerce_to_raw_ptr(a, b, b_mutbl); } ty::Ref(r_b, _, mutbl_b) => { - return self.coerce_to_ref(a, b, mutbl_b, r_b); + return self.coerce_to_ref(a, b, r_b, mutbl_b); } ty::Adt(pin, _) if self.tcx.features().pin_ergonomics() @@ -301,7 +304,7 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { } _ => { // Otherwise, just use unification rules. - self.unify(a, b, LeakCheck::Default) + self.unify(a, b, ForceLeakCheck::No) } } } @@ -325,12 +328,19 @@ fn coerce_from_inference_variable(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResu )); }; - let target_ty = self.use_lub.then(|| self.next_ty_var(self.cause.span)).unwrap_or(b); - - push_coerce_obligation(a, target_ty); - if self.use_lub { + let target_ty = if self.use_lub { + // When computing the lub, we create a new target + // and coerce both `a` and `b` to it. + let target_ty = self.next_ty_var(self.cause.span); + push_coerce_obligation(a, target_ty); push_coerce_obligation(b, target_ty); - } + target_ty + } else { + // When subtyping, we don't need to create a new target + // as we only coerce `a` to `b`. + push_coerce_obligation(a, b); + b + }; debug!( "coerce_from_inference_variable: two inference variables, target_ty={:?}, obligations={:?}", @@ -340,7 +350,7 @@ fn coerce_from_inference_variable(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResu } else { // One unresolved type variable: just apply subtyping, we may be able // to do something useful. - self.unify(a, b, LeakCheck::Default) + self.unify(a, b, ForceLeakCheck::No) } } @@ -355,8 +365,8 @@ fn coerce_to_ref( &self, a: Ty<'tcx>, b: Ty<'tcx>, - mutbl_b: hir::Mutability, r_b: ty::Region<'tcx>, + mutbl_b: hir::Mutability, ) -> CoerceResult<'tcx> { debug!("coerce_to_ref(a={:?}, b={:?})", a, b); debug_assert!(self.shallow_resolve(a) == a); @@ -367,7 +377,7 @@ fn coerce_to_ref( coerce_mutbls(mutbl, mutbl_b)?; (r_a, ty::TypeAndMut { ty, mutbl }) } - _ => return self.unify(a, b, LeakCheck::Default), + _ => return self.unify(a, b, ForceLeakCheck::No), }; // Look at each step in the `Deref` chain and check if @@ -418,7 +428,7 @@ fn coerce_to_ref( // the `Target` type with the pointee of `b`. This is necessary // to properly account for the differing variances of the pointees // of `&` vs `&mut` references. - match self.unify_raw(autorefd_deref_ty, b, LeakCheck::Default) { + match self.unify_raw(autorefd_deref_ty, b, ForceLeakCheck::No) { Ok(ok) => Some(ok), Err(err) => { if first_error.is_none() { @@ -473,10 +483,13 @@ fn coerce_to_ref( obligations.extend(o); obligations.extend(autoderef.into_obligations()); + assert!( + matches!(coerced_a.kind(), ty::Ref(..)), + "expected a ref type, got {:?}", + coerced_a + ); + // Now apply the autoref - let ty::Ref(..) = coerced_a.kind() else { - span_bug!(self.cause.span, "expected a ref type, got {:?}", coerced_a); - }; let mutbl = AutoBorrowMutability::new(mutbl_b, self.allow_two_phase); adjustments .push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), target: coerced_a }); @@ -615,7 +628,7 @@ fn coerce_unsized(&self, source: Ty<'tcx>, target: Ty<'tcx>) -> CoerceResult<'tc target, reborrow.into_iter().flat_map(|(deref, autoref)| [deref, autoref]), Adjust::Pointer(PointerCoercion::Unsize), - LeakCheck::Default, + ForceLeakCheck::No, )?; // Create an obligation for `Source: CoerceUnsized`. @@ -830,7 +843,7 @@ fn coerce_to_pin_ref(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { // To complete the reborrow, we need to make sure we can unify the inner types, and if so we // add the adjustments. - self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b), LeakCheck::Default) + self.unify_and(a, b, [], Adjust::ReborrowPin(mut_b), ForceLeakCheck::No) } fn coerce_from_fn_pointer( @@ -846,9 +859,9 @@ fn coerce_from_fn_pointer( ty::FnPtr(_, b_hdr) if a_sig.safety().is_safe() && b_hdr.safety.is_unsafe() => { let a = self.tcx.safe_to_unsafe_fn_ty(a_sig); let adjust = Adjust::Pointer(PointerCoercion::UnsafeFnPointer); - self.unify_and(a, b, [], adjust, LeakCheck::Yes) + self.unify_and(a, b, [], adjust, ForceLeakCheck::Yes) } - _ => self.unify(a, b, LeakCheck::Yes), + _ => self.unify(a, b, ForceLeakCheck::Yes), } } @@ -861,20 +874,18 @@ fn coerce_from_fn_item(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { ty::FnPtr(_, b_hdr) => { let a_sig = self.sig_for_fn_def_coercion(a, Some(b_hdr.safety))?; - // FIXME: we shouldn't be normalizing here as coercion is inside of - // a probe. This can probably cause ICEs. let InferOk { value: a_sig, mut obligations } = self.at(&self.cause, self.param_env).normalize(a_sig); let a = Ty::new_fn_ptr(self.tcx, a_sig); let adjust = Adjust::Pointer(PointerCoercion::ReifyFnPointer(b_hdr.safety)); let InferOk { value, obligations: o2 } = - self.unify_and(a, b, [], adjust, LeakCheck::Yes)?; + self.unify_and(a, b, [], adjust, ForceLeakCheck::Yes)?; obligations.extend(o2); Ok(InferOk { value, obligations }) } - _ => self.unify(a, b, LeakCheck::Default), + _ => self.unify(a, b, ForceLeakCheck::No), } } @@ -893,9 +904,9 @@ fn coerce_closure_to_fn(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> { debug!("coerce_closure_to_fn(a={:?}, b={:?}, pty={:?})", a, b, pointer_ty); let adjust = Adjust::Pointer(PointerCoercion::ClosureFnPointer(safety)); - self.unify_and(pointer_ty, b, [], adjust, LeakCheck::Default) + self.unify_and(pointer_ty, b, [], adjust, ForceLeakCheck::No) } - _ => self.unify(a, b, LeakCheck::Default), + _ => self.unify(a, b, ForceLeakCheck::No), } } @@ -912,7 +923,7 @@ fn coerce_to_raw_ptr( let (is_ref, mt_a) = match *a.kind() { ty::Ref(_, ty, mutbl) => (true, ty::TypeAndMut { ty, mutbl }), ty::RawPtr(ty, mutbl) => (false, ty::TypeAndMut { ty, mutbl }), - _ => return self.unify(a, b, LeakCheck::Default), + _ => return self.unify(a, b, ForceLeakCheck::No), }; coerce_mutbls(mt_a.mutbl, mutbl_b)?; @@ -927,7 +938,7 @@ fn coerce_to_raw_ptr( b, [Adjustment { kind: Adjust::Deref(None), target: mt_a.ty }], Adjust::Borrow(AutoBorrow::RawPtr(mutbl_b)), - LeakCheck::Default, + ForceLeakCheck::No, ) } else if mt_a.mutbl != mutbl_b { self.unify_and( @@ -935,10 +946,10 @@ fn coerce_to_raw_ptr( b, [], Adjust::Pointer(PointerCoercion::MutToConstPointer), - LeakCheck::Default, + ForceLeakCheck::No, ) } else { - self.unify(a_raw, b, LeakCheck::Default) + self.unify(a_raw, b, ForceLeakCheck::No) } } } @@ -1037,7 +1048,7 @@ pub(crate) fn deref_steps_for_suggestion( // We don't ever need two-phase here since we throw out the result of the coercion. let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true); coerce.autoderef(DUMMY_SP, expr_ty).find_map(|(ty, steps)| { - self.probe(|_| coerce.unify_raw(ty, target, LeakCheck::Default)).ok().map(|_| steps) + self.probe(|_| coerce.unify_raw(ty, target, ForceLeakCheck::No)).ok().map(|_| steps) }) } diff --git a/tests/ui/coercion/hr_alias_normalization_leaking_vars.rs b/tests/ui/coercion/hr_alias_normalization_leaking_vars.rs index 43678fd876fb..71e8fdfcfa78 100644 --- a/tests/ui/coercion/hr_alias_normalization_leaking_vars.rs +++ b/tests/ui/coercion/hr_alias_normalization_leaking_vars.rs @@ -1,3 +1,5 @@ +// Regression test for #132765 +// // We have two function parameters with types: // - `&?0` // - `Box fn(>::Item)>` diff --git a/tests/ui/coercion/hr_alias_normalization_leaking_vars.stderr b/tests/ui/coercion/hr_alias_normalization_leaking_vars.stderr index 143cc6b29735..54da352c6503 100644 --- a/tests/ui/coercion/hr_alias_normalization_leaking_vars.stderr +++ b/tests/ui/coercion/hr_alias_normalization_leaking_vars.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `(): LendingIterator` is not satisfied - --> $DIR/hr_alias_normalization_leaking_vars.rs:32:31 + --> $DIR/hr_alias_normalization_leaking_vars.rs:34:31 | LL | LendingIterator::for_each(&(), f); | ------------------------- ^^^ the trait `LendingIterator` is not implemented for `()` @@ -7,13 +7,13 @@ LL | LendingIterator::for_each(&(), f); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/hr_alias_normalization_leaking_vars.rs:24:1 + --> $DIR/hr_alias_normalization_leaking_vars.rs:26:1 | LL | trait LendingIterator { | ^^^^^^^^^^^^^^^^^^^^^ error[E0308]: mismatched types - --> $DIR/hr_alias_normalization_leaking_vars.rs:32:36 + --> $DIR/hr_alias_normalization_leaking_vars.rs:34:36 | LL | LendingIterator::for_each(&(), f); | ------------------------- ^ expected `Box`, found fn item @@ -23,19 +23,19 @@ LL | LendingIterator::for_each(&(), f); = note: expected struct `Box fn(<() as LendingIterator>::Item<'a>)>` found fn item `fn(()) {f}` note: method defined here - --> $DIR/hr_alias_normalization_leaking_vars.rs:26:8 + --> $DIR/hr_alias_normalization_leaking_vars.rs:28:8 | LL | fn for_each(&self, _f: Box)>) {} | ^^^^^^^^ --------------------------- error[E0277]: the trait bound `(): LendingIterator` is not satisfied - --> $DIR/hr_alias_normalization_leaking_vars.rs:32:36 + --> $DIR/hr_alias_normalization_leaking_vars.rs:34:36 | LL | LendingIterator::for_each(&(), f); | ^ the trait `LendingIterator` is not implemented for `()` | help: this trait has no implementations, consider adding one - --> $DIR/hr_alias_normalization_leaking_vars.rs:24:1 + --> $DIR/hr_alias_normalization_leaking_vars.rs:26:1 | LL | trait LendingIterator { | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/coercion/leak_check_fndef_lub.rs b/tests/ui/coercion/leak_check_fndef_lub.rs index b13a62db20be..2385117f5e2f 100644 --- a/tests/ui/coercion/leak_check_fndef_lub.rs +++ b/tests/ui/coercion/leak_check_fndef_lub.rs @@ -9,9 +9,7 @@ macro_rules! lub { }; } - // These don't currently lub but could in theory one day. - // If that happens this test should be adjusted to use - // fn ptrs that can't be lub'd. + // Unused parameters on FnDefs are considered invariant let lhs = foo:: fn(&'static (), &'a ())>; let rhs = foo:: fn(&'a (), &'static ())>; diff --git a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs index e2743bf39ab6..0e79ca28654e 100644 --- a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs +++ b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.rs @@ -9,9 +9,7 @@ macro_rules! lub { }; } - // These don't currently lub but could in theory one day. - // If that happens this test should be adjusted to use - // fn ptrs that can't be lub'd. + // Unused parameters on FnDefs are considered invariant let lhs = foo:: fn(&'static (), &'a ())>; let rhs = foo:: fn(&'a (), &'static ())>; diff --git a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr index 60e63ee93269..f7336d49e8ca 100644 --- a/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr +++ b/tests/ui/coercion/leak_check_fndef_lub_deadcode_breakage.stderr @@ -1,5 +1,5 @@ error[E0512]: cannot transmute between types of different sizes, or dependently-sized types - --> $DIR/leak_check_fndef_lub_deadcode_breakage.rs:27:14 + --> $DIR/leak_check_fndef_lub_deadcode_breakage.rs:25:14 | LL | unsafe { std::mem::transmute::<_, ()>(lubbed) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/coercion/lub_closures_before_fnptr_coercion.rs b/tests/ui/coercion/lub_closures_before_fnptr_coercion.rs deleted file mode 100644 index b2c421ece570..000000000000 --- a/tests/ui/coercion/lub_closures_before_fnptr_coercion.rs +++ /dev/null @@ -1,70 +0,0 @@ -//@ check-pass -//@ compile-flags: -Znext-solver - -#![feature(type_alias_impl_trait)] - -// Test that when lubbing two equal closure tys with different -// structural identities (i.e. `PartialEq::eq` on `ty::Ty` would be false) -// we don't coerce-lub to a fnptr. -// -// Most of this test is involved jank to be able to leak the hidden type -// of an opaque with a hidden type of `Closure`. This then allows -// us to substitute `C1` and `C2` for arbitrary types in the parent scope. -// -// See: - -struct WaddupGamers(Option, U); -impl, U> Unpin for WaddupGamers {} -unsafe impl, U> Send for WaddupGamers {} -pub trait Leak { - type Unpin; - type Send; -} -impl Leak for (T,) { - type Unpin = T; - type Send = T; -} -fn define() -> impl Sized { - WaddupGamers(None::, || ()) -} - -fn require_unpin(_: T) {} -fn require_send(_: T) {} -fn mk() -> T { todo!() } - -type NameMe = impl Sized; -type NameMe2 = impl Sized; - -#[define_opaque(NameMe, NameMe2)] -fn leak() -where - T: Leak, Send = NameMe2>, -{ - require_unpin(define:: fn(&'a ())>()); - require_send(define:: fn(&'a ())>()); - - // This is the actual logic for lubbing two closures - // with syntactically different `ty::Ty`s: - - // lhs: Closure fn(&'a1 ())> - let lhs = mk::>(); - // lhs: Closure fn(&'a2 ())> - let rhs = mk::>(); - - macro_rules! lub { - ($lhs:expr, $rhs:expr) => { - if true { $lhs } else { $rhs } - }; - } - - // Lubbed to either: - // - `Closure fn(&'a ())>` - // - `fn(&())` - let lubbed = lub!(lhs, rhs); - - // Use transmute to assert the size of `lubbed` is (), i.e. - // that it is a ZST closure type not a fnptr. - unsafe { std::mem::transmute::<_, ()>(lubbed) }; -} - -fn main() {} From f30eced2e3910139108f5751ea1f6a22829358c6 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Thu, 20 Nov 2025 02:06:38 +0900 Subject: [PATCH 12/26] Wrap binding name in parentheses in for-loop mut suggestion --- .../src/diagnostics/conflict_errors.rs | 16 ++++++++++++++- ...rowck-for-loop-deref-pattern-assignment.rs | 10 ++++++++++ ...k-for-loop-deref-pattern-assignment.stderr | 20 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.rs create mode 100644 tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 9a8927c10297..7bb50a3ec4d5 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -3940,12 +3940,26 @@ pub(crate) fn report_illegal_reassignment( if let Some(decl) = local_decl && decl.can_be_made_mutable() { + let message = if matches!( + decl.local_info(), + LocalInfo::User(BindingForm::Var(VarBindingForm { + opt_match_place: Some((_, match_span)), + .. + })) if matches!(match_span.desugaring_kind(), Some(DesugaringKind::ForLoop)) + ) && let Ok(binding_name) = + self.infcx.tcx.sess.source_map().span_to_snippet(decl.source_info.span) + { + format!("(mut {}) ", binding_name) + } else { + "mut ".to_string() + }; err.span_suggestion_verbose( decl.source_info.span.shrink_to_lo(), "consider making this binding mutable", - "mut ".to_string(), + message, Applicability::MachineApplicable, ); + if !from_arg && matches!( decl.local_info(), diff --git a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.rs b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.rs new file mode 100644 index 000000000000..fc4f1e4eacb9 --- /dev/null +++ b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.rs @@ -0,0 +1,10 @@ +//! regression test for +//! Ensure the diagnostic suggests `for &(mut x) ...` (parenthesized) instead of `&mut x`. + +fn main() { + let nums: &[u32] = &[1, 2, 3]; + for &num in nums { + num *= 2; //~ ERROR cannot assign twice to immutable variable `num` + println!("{num}"); + } +} diff --git a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr new file mode 100644 index 000000000000..cd53e297e348 --- /dev/null +++ b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr @@ -0,0 +1,20 @@ +error[E0384]: cannot assign twice to immutable variable `num` + --> $DIR/borrowck-for-loop-deref-pattern-assignment.rs:7:9 + | +LL | for &num in nums { + | --- first assignment to `num` +LL | num *= 2; + | ^^^^^^^^ cannot assign twice to immutable variable + | +help: consider making this binding mutable + | +LL | for &(mut num) num in nums { + | +++++++++ +help: to modify the original value, take a borrow instead + | +LL | for &ref mut num in nums { + | +++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0384`. From d49075f083d39c1ab91256c6945cc5c854ecee84 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Thu, 4 Dec 2025 15:13:40 +0900 Subject: [PATCH 13/26] Suppress `ref mut` suggestion for for-loop bindings --- .../src/diagnostics/conflict_errors.rs | 19 +++++++++++-------- ...k-for-loop-deref-pattern-assignment.stderr | 4 ---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 7bb50a3ec4d5..f8a6fafbe78a 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -3940,14 +3940,16 @@ pub(crate) fn report_illegal_reassignment( if let Some(decl) = local_decl && decl.can_be_made_mutable() { - let message = if matches!( - decl.local_info(), - LocalInfo::User(BindingForm::Var(VarBindingForm { - opt_match_place: Some((_, match_span)), - .. - })) if matches!(match_span.desugaring_kind(), Some(DesugaringKind::ForLoop)) - ) && let Ok(binding_name) = - self.infcx.tcx.sess.source_map().span_to_snippet(decl.source_info.span) + let is_for_loop = matches!( + decl.local_info(), + LocalInfo::User(BindingForm::Var(VarBindingForm { + opt_match_place: Some((_, match_span)), + .. + })) if matches!(match_span.desugaring_kind(), Some(DesugaringKind::ForLoop)) + ); + let message = if is_for_loop + && let Ok(binding_name) = + self.infcx.tcx.sess.source_map().span_to_snippet(decl.source_info.span) { format!("(mut {}) ", binding_name) } else { @@ -3961,6 +3963,7 @@ pub(crate) fn report_illegal_reassignment( ); if !from_arg + && !is_for_loop && matches!( decl.local_info(), LocalInfo::User(BindingForm::Var(VarBindingForm { diff --git a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr index cd53e297e348..fa230134df55 100644 --- a/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr +++ b/tests/ui/borrowck/borrowck-for-loop-deref-pattern-assignment.stderr @@ -10,10 +10,6 @@ help: consider making this binding mutable | LL | for &(mut num) num in nums { | +++++++++ -help: to modify the original value, take a borrow instead - | -LL | for &ref mut num in nums { - | +++++++ error: aborting due to 1 previous error From e513ce3fb47b6dd89f37edf64f11720f6d1e7dfe Mon Sep 17 00:00:00 2001 From: Wafarm Date: Thu, 4 Dec 2025 14:58:30 +0800 Subject: [PATCH 14/26] Check identifiers defined in macros when suggesting identifiers hidden by hygiene --- .../rustc_resolve/src/late/diagnostics.rs | 14 +++++++ tests/ui/hygiene/pattern-macro.stderr | 10 +++++ .../macros/macro-hygiene-help-issue-149604.rs | 9 +++++ .../macro-hygiene-help-issue-149604.stderr | 38 +++++++++++++++++++ .../proc-macro/gen-macro-rules-hygiene.stderr | 10 +++++ tests/ui/proc-macro/mixed-site-span.stderr | 7 ++++ 6 files changed, 88 insertions(+) create mode 100644 tests/ui/macros/macro-hygiene-help-issue-149604.rs create mode 100644 tests/ui/macros/macro-hygiene-help-issue-149604.stderr diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 2b85639376d9..3ab6c7dcc006 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1155,6 +1155,7 @@ fn suggest_ident_hidden_by_hygiene(&self, err: &mut Diag<'_>, path: &[Segment], let callsite_span = span.source_callsite(); for rib in self.ribs[ValueNS].iter().rev() { for (binding_ident, _) in &rib.bindings { + // Case 1: the identifier is defined in the same scope as the macro is called if binding_ident.name == ident.name && !binding_ident.span.eq_ctxt(span) && !binding_ident.span.from_expansion() @@ -1166,6 +1167,19 @@ fn suggest_ident_hidden_by_hygiene(&self, err: &mut Diag<'_>, path: &[Segment], ); return; } + + // Case 2: the identifier is defined in a macro call in the same scope + if binding_ident.name == ident.name + && binding_ident.span.from_expansion() + && binding_ident.span.source_callsite().eq_ctxt(callsite_span) + && binding_ident.span.source_callsite().lo() < callsite_span.lo() + { + err.span_help( + binding_ident.span, + "an identifier with the same name is defined here, but is not accessible due to macro hygiene", + ); + return; + } } } } diff --git a/tests/ui/hygiene/pattern-macro.stderr b/tests/ui/hygiene/pattern-macro.stderr index a9764cea49e5..047244ba9ede 100644 --- a/tests/ui/hygiene/pattern-macro.stderr +++ b/tests/ui/hygiene/pattern-macro.stderr @@ -3,6 +3,16 @@ error[E0425]: cannot find value `x` in this scope | LL | x + 1; | ^ not found in this scope + | +help: an identifier with the same name is defined here, but is not accessible due to macro hygiene + --> $DIR/pattern-macro.rs:1:28 + | +LL | macro_rules! foo { () => ( x ) } + | ^ +... +LL | let foo!() = 2; + | ------ in this macro invocation + = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/tests/ui/macros/macro-hygiene-help-issue-149604.rs b/tests/ui/macros/macro-hygiene-help-issue-149604.rs new file mode 100644 index 000000000000..5700218cc867 --- /dev/null +++ b/tests/ui/macros/macro-hygiene-help-issue-149604.rs @@ -0,0 +1,9 @@ +macro_rules! let_it { {} => { let it = (); } } +macro_rules! print_it { {} => { println!("{:?}", it); } } +//~^ ERROR cannot find value `it` in this scope + +fn main() { + let_it!(); + let () = it; //~ ERROR cannot find value `it` in this scope + print_it!(); +} diff --git a/tests/ui/macros/macro-hygiene-help-issue-149604.stderr b/tests/ui/macros/macro-hygiene-help-issue-149604.stderr new file mode 100644 index 000000000000..dc95cb7a43f0 --- /dev/null +++ b/tests/ui/macros/macro-hygiene-help-issue-149604.stderr @@ -0,0 +1,38 @@ +error[E0425]: cannot find value `it` in this scope + --> $DIR/macro-hygiene-help-issue-149604.rs:7:14 + | +LL | let () = it; + | ^^ not found in this scope + | +help: an identifier with the same name is defined here, but is not accessible due to macro hygiene + --> $DIR/macro-hygiene-help-issue-149604.rs:1:35 + | +LL | macro_rules! let_it { {} => { let it = (); } } + | ^^ +... +LL | let_it!(); + | --------- in this macro invocation + = note: this error originates in the macro `let_it` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0425]: cannot find value `it` in this scope + --> $DIR/macro-hygiene-help-issue-149604.rs:2:50 + | +LL | macro_rules! print_it { {} => { println!("{:?}", it); } } + | ^^ not found in this scope +... +LL | print_it!(); + | ----------- in this macro invocation + | +help: an identifier with the same name is defined here, but is not accessible due to macro hygiene + --> $DIR/macro-hygiene-help-issue-149604.rs:1:35 + | +LL | macro_rules! let_it { {} => { let it = (); } } + | ^^ +... +LL | let_it!(); + | --------- in this macro invocation + = note: this error originates in the macro `print_it` which comes from the expansion of the macro `let_it` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/proc-macro/gen-macro-rules-hygiene.stderr b/tests/ui/proc-macro/gen-macro-rules-hygiene.stderr index 17171ad5c5cc..ed8ee4dc52cb 100644 --- a/tests/ui/proc-macro/gen-macro-rules-hygiene.stderr +++ b/tests/ui/proc-macro/gen-macro-rules-hygiene.stderr @@ -30,6 +30,16 @@ error[E0425]: cannot find value `local_def` in this scope | LL | local_def; | ^^^^^^^^^ help: a local variable with a similar name exists: `local_use` + | +help: an identifier with the same name is defined here, but is not accessible due to macro hygiene + --> $DIR/gen-macro-rules-hygiene.rs:13:1 + | +LL | gen_macro_rules!(); + | ^^^^^^^^^^^^^^^^^^ +... +LL | generated!(); + | ------------ in this macro invocation + = note: this error originates in the macro `generated` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 3 previous errors diff --git a/tests/ui/proc-macro/mixed-site-span.stderr b/tests/ui/proc-macro/mixed-site-span.stderr index d5cf484f6dd0..97e3f3e3dea8 100644 --- a/tests/ui/proc-macro/mixed-site-span.stderr +++ b/tests/ui/proc-macro/mixed-site-span.stderr @@ -606,6 +606,13 @@ error[E0425]: cannot find value `local_def` in this scope | LL | local_def; | ^^^^^^^^^ help: a local variable with a similar name exists: `local_use` + | +help: an identifier with the same name is defined here, but is not accessible due to macro hygiene + --> $DIR/mixed-site-span.rs:23:9 + | +LL | proc_macro_rules!(); + | ^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `proc_macro_rules` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 52 previous errors From f7b3c1d3c05c683ea22aca5db0726036786745ba Mon Sep 17 00:00:00 2001 From: beetrees Date: Fri, 4 Jul 2025 17:44:15 +0100 Subject: [PATCH 15/26] Rework `c_variadic` --- .../rustc_codegen_ssa/src/traits/intrinsic.rs | 4 +- library/core/src/ffi/mod.rs | 2 +- library/core/src/ffi/va_list.rs | 183 ++++--------- library/core/src/intrinsics/mod.rs | 8 +- library/std/src/ffi/mod.rs | 2 +- .../src/directives/directive_names.rs | 1 + tests/auxiliary/rust_test_helpers.c | 4 + tests/codegen-llvm/cffi/c-variadic-copy.rs | 4 +- tests/codegen-llvm/cffi/c-variadic-opt.rs | 8 +- tests/codegen-llvm/cffi/c-variadic.rs | 12 +- .../c-link-to-rust-va-list-fn/checkrust.rs | 15 +- tests/ui/abi/variadic-ffi.rs | 32 ++- .../pass-by-value-abi.aarch64.stderr | 80 ++++++ tests/ui/c-variadic/pass-by-value-abi.rs | 46 ++++ .../c-variadic/pass-by-value-abi.win.stderr | 83 ++++++ .../pass-by-value-abi.x86_64.stderr | 240 ++++++++++++++++++ tests/ui/c-variadic/variadic-ffi-4.rs | 19 +- tests/ui/c-variadic/variadic-ffi-4.stderr | 121 ++------- .../macro-dotdotdot-may-not-begin-a-type.rs | 2 +- .../variadic-ffi-semantic-restrictions.rs | 6 +- .../variadic-ffi-semantic-restrictions.stderr | 6 +- tests/ui/thir-print/c-variadic.stdout | 4 +- 22 files changed, 591 insertions(+), 291 deletions(-) create mode 100644 tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr create mode 100644 tests/ui/c-variadic/pass-by-value-abi.rs create mode 100644 tests/ui/c-variadic/pass-by-value-abi.win.stderr create mode 100644 tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr diff --git a/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs b/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs index c5ecf43046c7..187e4b90656a 100644 --- a/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/traits/intrinsic.rs @@ -36,10 +36,10 @@ fn type_checked_load( vtable_byte_offset: u64, typeid: Self::Metadata, ) -> Self::Value; - /// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in + /// Trait method used to inject `va_start` on the "spoofed" `VaList` in /// Rust defined C-variadic functions. fn va_start(&mut self, val: Self::Value) -> Self::Value; - /// Trait method used to inject `va_end` on the "spoofed" `VaListImpl` before + /// Trait method used to inject `va_end` on the "spoofed" `VaList` before /// Rust defined C-variadic functions return. fn va_end(&mut self, val: Self::Value) -> Self::Value; } diff --git a/library/core/src/ffi/mod.rs b/library/core/src/ffi/mod.rs index 1356ca217c9a..f1b928da7ef3 100644 --- a/library/core/src/ffi/mod.rs +++ b/library/core/src/ffi/mod.rs @@ -28,7 +28,7 @@ issue = "44930", reason = "the `c_variadic` feature has not been properly tested on all supported platforms" )] -pub use self::va_list::{VaArgSafe, VaList, VaListImpl}; +pub use self::va_list::{VaArgSafe, VaList}; #[unstable( feature = "c_variadic", diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 233a2ee3e484..449e62ac00d3 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -4,15 +4,15 @@ #[cfg(not(target_arch = "xtensa"))] use crate::ffi::c_void; -#[allow(unused_imports)] use crate::fmt; -use crate::intrinsics::{va_arg, va_copy, va_end}; -use crate::marker::{PhantomData, PhantomInvariantLifetime}; -use crate::ops::{Deref, DerefMut}; +use crate::intrinsics::{va_arg, va_copy}; +use crate::marker::PhantomCovariantLifetime; -// The name is WIP, using `VaListImpl` for now. -// // Most targets explicitly specify the layout of `va_list`, this layout is matched here. +// For `va_list`s which are single-element array in C (and therefore experience array-to-pointer +// decay when passed as arguments in C), the `VaList` struct is annotated with +// `#[rustc_pass_indirectly_in_non_rustic_abis]`. This ensures that the compiler uses the correct +// ABI for functions like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly. crate::cfg_select! { all( target_arch = "aarch64", @@ -27,66 +27,60 @@ /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { - stack: *mut c_void, - gr_top: *mut c_void, - vr_top: *mut c_void, + struct VaListInner { + stack: *const c_void, + gr_top: *const c_void, + vr_top: *const c_void, gr_offs: i32, vr_offs: i32, - _marker: PhantomInvariantLifetime<'f>, } } all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => { /// PowerPC ABI implementation of a `va_list`. #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { + #[rustc_pass_indirectly_in_non_rustic_abis] + struct VaListInner { gpr: u8, fpr: u8, reserved: u16, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomInvariantLifetime<'f>, + overflow_arg_area: *const c_void, + reg_save_area: *const c_void, } } target_arch = "s390x" => { /// s390x ABI implementation of a `va_list`. #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { + #[rustc_pass_indirectly_in_non_rustic_abis] + struct VaListInner { gpr: i64, fpr: i64, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomInvariantLifetime<'f>, + overflow_arg_area: *const c_void, + reg_save_area: *const c_void, } } all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => { /// x86_64 ABI implementation of a `va_list`. #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { + #[rustc_pass_indirectly_in_non_rustic_abis] + struct VaListInner { gp_offset: i32, fp_offset: i32, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomInvariantLifetime<'f>, + overflow_arg_area: *const c_void, + reg_save_area: *const c_void, } } target_arch = "xtensa" => { /// Xtensa ABI implementation of a `va_list`. #[repr(C)] #[derive(Debug)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { - stk: *mut i32, - reg: *mut i32, + #[rustc_pass_indirectly_in_non_rustic_abis] + struct VaListInner { + stk: *const i32, + reg: *const i32, ndx: i32, - _marker: PhantomInvariantLifetime<'f>, } } @@ -95,94 +89,32 @@ pub struct VaListImpl<'f> { // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599) // - windows // - uefi - // - any other target for which we don't specify the `VaListImpl` above + // - any other target for which we don't specify the `VaListInner` above // // In this implementation the `va_list` type is just an alias for an opaque pointer. // That pointer is probably just the next variadic argument on the caller's stack. _ => { /// Basic implementation of a `va_list`. #[repr(transparent)] - #[lang = "va_list"] - pub struct VaListImpl<'f> { - ptr: *mut c_void, - - // Invariant over `'f`, so each `VaListImpl<'f>` object is tied to - // the region of the function it's defined in - _marker: PhantomInvariantLifetime<'f>, - } - - impl<'f> fmt::Debug for VaListImpl<'f> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "va_list* {:p}", self.ptr) - } - } - } -} - -crate::cfg_select! { - all( - any( - target_arch = "aarch64", - target_arch = "powerpc", - target_arch = "s390x", - target_arch = "x86_64" - ), - not(target_arch = "xtensa"), - any(not(target_arch = "aarch64"), not(target_vendor = "apple")), - not(target_family = "wasm"), - not(target_os = "uefi"), - not(windows), - ) => { - /// A wrapper for a `va_list` - #[repr(transparent)] #[derive(Debug)] - pub struct VaList<'a, 'f: 'a> { - inner: &'a mut VaListImpl<'f>, - _marker: PhantomData<&'a mut VaListImpl<'f>>, - } - - - impl<'f> VaListImpl<'f> { - /// Converts a [`VaListImpl`] into a [`VaList`] that is binary-compatible with C's `va_list`. - #[inline] - pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> { - VaList { inner: self, _marker: PhantomData } - } - } - } - - _ => { - /// A wrapper for a `va_list` - #[repr(transparent)] - #[derive(Debug)] - pub struct VaList<'a, 'f: 'a> { - inner: VaListImpl<'f>, - _marker: PhantomData<&'a mut VaListImpl<'f>>, - } - - impl<'f> VaListImpl<'f> { - /// Converts a [`VaListImpl`] into a [`VaList`] that is binary-compatible with C's `va_list`. - #[inline] - pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> { - VaList { inner: VaListImpl { ..*self }, _marker: PhantomData } - } + struct VaListInner { + ptr: *const c_void, } } } -impl<'a, 'f: 'a> Deref for VaList<'a, 'f> { - type Target = VaListImpl<'f>; - - #[inline] - fn deref(&self) -> &VaListImpl<'f> { - &self.inner - } +/// A variable argument list, equivalent to `va_list` in C. +#[repr(transparent)] +#[lang = "va_list"] +pub struct VaList<'a> { + inner: VaListInner, + _marker: PhantomCovariantLifetime<'a>, } -impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> { - #[inline] - fn deref_mut(&mut self) -> &mut VaListImpl<'f> { - &mut self.inner +impl fmt::Debug for VaList<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // No need to include `_marker` in debug output. + f.debug_tuple("VaList").field(&self.inner).finish() } } @@ -203,7 +135,7 @@ impl Sealed for *mut T {} impl Sealed for *const T {} } -/// Types that are valid to read using [`VaListImpl::arg`]. +/// Types that are valid to read using [`VaList::arg`]. /// /// # Safety /// @@ -238,7 +170,7 @@ unsafe impl VaArgSafe for f64 {} unsafe impl VaArgSafe for *mut T {} unsafe impl VaArgSafe for *const T {} -impl<'f> VaListImpl<'f> { +impl<'f> VaList<'f> { /// Advance to and read the next variable argument. /// /// # Safety @@ -258,27 +190,13 @@ pub unsafe fn arg(&mut self) -> T { // SAFETY: the caller must uphold the safety contract for `va_arg`. unsafe { va_arg(self) } } - - /// Copies the `va_list` at the current location. - pub unsafe fn with_copy(&self, f: F) -> R - where - F: for<'copy> FnOnce(VaList<'copy, 'f>) -> R, - { - let mut ap = self.clone(); - let ret = f(ap.as_va_list()); - // SAFETY: the caller must uphold the safety contract for `va_end`. - unsafe { - va_end(&mut ap); - } - ret - } } -impl<'f> Clone for VaListImpl<'f> { +impl<'f> Clone for VaList<'f> { #[inline] fn clone(&self) -> Self { let mut dest = crate::mem::MaybeUninit::uninit(); - // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal + // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal. unsafe { va_copy(dest.as_mut_ptr(), self); dest.assume_init() @@ -286,18 +204,11 @@ fn clone(&self) -> Self { } } -impl<'f> Drop for VaListImpl<'f> { +impl<'f> Drop for VaList<'f> { fn drop(&mut self) { - // FIXME: this should call `va_end`, but there's no clean way to - // guarantee that `drop` always gets inlined into its caller, - // so the `va_end` would get directly called from the same function as - // the corresponding `va_copy`. `man va_end` states that C requires this, - // and LLVM basically follows the C semantics, so we need to make sure - // that `va_end` is always called from the same function as `va_copy`. - // For more details, see https://github.com/rust-lang/rust/pull/59625 - // and https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic. - // - // This works for now, since `va_end` is a no-op on all current LLVM targets. + // Rust requires that not calling `va_end` on a `va_list` does not cause undefined behaviour + // (as it is safe to leak values). As `va_end` is a no-op on all current LLVM targets, this + // destructor is empty. } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 2115c5c9a85d..7571f4a1fc12 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -54,7 +54,7 @@ )] #![allow(missing_docs)] -use crate::ffi::va_list::{VaArgSafe, VaListImpl}; +use crate::ffi::va_list::{VaArgSafe, VaList}; use crate::marker::{ConstParamTy, Destruct, DiscriminantKind, PointeeSized, Tuple}; use crate::{mem, ptr}; @@ -3447,7 +3447,7 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_copy<'f>(dest: *mut VaListImpl<'f>, src: &VaListImpl<'f>); +pub unsafe fn va_copy<'f>(dest: *mut VaList<'f>, src: &VaList<'f>); /// Loads an argument of type `T` from the `va_list` `ap` and increment the /// argument `ap` points to. @@ -3465,7 +3465,7 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_arg(ap: &mut VaListImpl<'_>) -> T; +pub unsafe fn va_arg(ap: &mut VaList<'_>) -> T; /// Destroy the arglist `ap` after initialization with `va_start` or `va_copy`. /// @@ -3475,4 +3475,4 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_end(ap: &mut VaListImpl<'_>); +pub unsafe fn va_end(ap: &mut VaList<'_>); diff --git a/library/std/src/ffi/mod.rs b/library/std/src/ffi/mod.rs index f44e12d48add..999bd5e63dc4 100644 --- a/library/std/src/ffi/mod.rs +++ b/library/std/src/ffi/mod.rs @@ -172,7 +172,7 @@ all supported platforms", issue = "44930" )] -pub use core::ffi::{VaArgSafe, VaList, VaListImpl}; +pub use core::ffi::{VaArgSafe, VaList}; #[stable(feature = "core_ffi_c", since = "1.64.0")] pub use core::ffi::{ c_char, c_double, c_float, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs index 7b1c0a18670d..e9824edfef60 100644 --- a/src/tools/compiletest/src/directives/directive_names.rs +++ b/src/tools/compiletest/src/directives/directive_names.rs @@ -121,6 +121,7 @@ "ignore-thumbv8m.base-none-eabi", "ignore-thumbv8m.main-none-eabi", "ignore-tvos", + "ignore-uefi", "ignore-unix", "ignore-unknown", "ignore-uwp", diff --git a/tests/auxiliary/rust_test_helpers.c b/tests/auxiliary/rust_test_helpers.c index 34cc7fd5dfbe..cd10d6b98ca7 100644 --- a/tests/auxiliary/rust_test_helpers.c +++ b/tests/auxiliary/rust_test_helpers.c @@ -314,6 +314,10 @@ double rust_interesting_average(uint64_t n, ...) { return sum; } +int32_t rust_va_list_next_i32(va_list* ap) { + return va_arg(*ap, int32_t); +} + int32_t rust_int8_to_int32(int8_t x) { return (int32_t)x; } diff --git a/tests/codegen-llvm/cffi/c-variadic-copy.rs b/tests/codegen-llvm/cffi/c-variadic-copy.rs index 4c61c4fcf68d..0cbdcb4bbb85 100644 --- a/tests/codegen-llvm/cffi/c-variadic-copy.rs +++ b/tests/codegen-llvm/cffi/c-variadic-copy.rs @@ -1,4 +1,4 @@ -// Tests that `VaListImpl::clone` gets inlined into a call to `llvm.va_copy` +// Tests that `VaList::clone` gets inlined into a call to `llvm.va_copy` #![crate_type = "lib"] #![feature(c_variadic)] @@ -12,5 +12,5 @@ pub unsafe extern "C" fn clone_variadic(ap: VaList) { let mut ap2 = ap.clone(); // CHECK: call void @llvm.va_copy - foreign_c_variadic_1(ap2.as_va_list(), 42i32); + foreign_c_variadic_1(ap2, 42i32); } diff --git a/tests/codegen-llvm/cffi/c-variadic-opt.rs b/tests/codegen-llvm/cffi/c-variadic-opt.rs index 7e544ee7f37d..3cc0c3e9f9bd 100644 --- a/tests/codegen-llvm/cffi/c-variadic-opt.rs +++ b/tests/codegen-llvm/cffi/c-variadic-opt.rs @@ -10,21 +10,21 @@ } // Ensure that `va_start` and `va_end` are properly injected even -// when the "spoofed" `VaListImpl` is not used. +// when the "spoofed" `VaList` is not used. #[no_mangle] pub unsafe extern "C" fn c_variadic_no_use(fmt: *const i8, mut ap: ...) -> i32 { // CHECK: call void @llvm.va_start - vprintf(fmt, ap.as_va_list()) + vprintf(fmt, ap) // CHECK: call void @llvm.va_end } -// Check that `VaListImpl::clone` gets inlined into a direct call to `llvm.va_copy` +// Check that `VaList::clone` gets inlined into a direct call to `llvm.va_copy` #[no_mangle] pub unsafe extern "C" fn c_variadic_clone(fmt: *const i8, mut ap: ...) -> i32 { // CHECK: call void @llvm.va_start let mut ap2 = ap.clone(); // CHECK: call void @llvm.va_copy - let res = vprintf(fmt, ap2.as_va_list()); + let res = vprintf(fmt, ap2); res // CHECK: call void @llvm.va_end } diff --git a/tests/codegen-llvm/cffi/c-variadic.rs b/tests/codegen-llvm/cffi/c-variadic.rs index 140d2f37f469..3ce421eb4a5c 100644 --- a/tests/codegen-llvm/cffi/c-variadic.rs +++ b/tests/codegen-llvm/cffi/c-variadic.rs @@ -1,6 +1,6 @@ //@ needs-unwind //@ compile-flags: -C no-prepopulate-passes -Copt-level=0 -// +//@ min-llvm-version: 21 #![crate_type = "lib"] #![feature(c_variadic)] @@ -25,23 +25,23 @@ } // Ensure that we do not remove the `va_list` passed to the foreign function when -// removing the "spoofed" `VaListImpl` that is used by Rust defined C-variadics. +// removing the "spoofed" `VaList` that is used by Rust defined C-variadics. pub unsafe extern "C" fn use_foreign_c_variadic_1_0(ap: VaList) { - // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap) + // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %0) foreign_c_variadic_1(ap); } pub unsafe extern "C" fn use_foreign_c_variadic_1_1(ap: VaList) { - // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap, [[PARAM]] 42) + // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %0, [[PARAM]] 42) foreign_c_variadic_1(ap, 42i32); } pub unsafe extern "C" fn use_foreign_c_variadic_1_2(ap: VaList) { - // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap, [[PARAM]] 2, [[PARAM]] 42) + // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %0, [[PARAM]] 2, [[PARAM]] 42) foreign_c_variadic_1(ap, 2i32, 42i32); } pub unsafe extern "C" fn use_foreign_c_variadic_1_3(ap: VaList) { - // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap, [[PARAM]] 2, [[PARAM]] 42, [[PARAM]] 0) + // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %0, [[PARAM]] 2, [[PARAM]] 42, [[PARAM]] 0) foreign_c_variadic_1(ap, 2i32, 42i32, 0i32); } diff --git a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs index 63d8d713d622..dd2d09470994 100644 --- a/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs +++ b/tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs @@ -2,7 +2,7 @@ #![feature(c_variadic)] #![feature(cfg_select)] -use std::ffi::{CStr, CString, VaList, VaListImpl, c_char, c_double, c_int, c_long, c_longlong}; +use std::ffi::{CStr, CString, VaList, c_char, c_double, c_int, c_long, c_longlong}; macro_rules! continue_if { ($cond:expr) => { @@ -58,11 +58,8 @@ unsafe fn compare_c_str(ptr: *const c_char, val: &str) -> bool { continue_if!(ap.arg::() == 16); continue_if!(ap.arg::() == 'A' as c_int); continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Skip Me!")); - ap.with_copy( - |mut ap| { - if compare_c_str(ap.arg::<*const c_char>(), "Correct") { 0 } else { 0xff } - }, - ) + let mut ap = ap.clone(); + if compare_c_str(ap.arg::<*const c_char>(), "Correct") { 0 } else { 0xff } } #[unsafe(no_mangle)] @@ -153,8 +150,8 @@ unsafe fn compare_c_str(ptr: *const c_char, val: &str) -> bool { unsafe extern "C" { fn test_variadic(_: c_int, ...) -> usize; fn test_va_list_by_value(_: VaList) -> usize; - fn test_va_list_by_pointer(_: *mut VaListImpl) -> usize; - fn test_va_list_by_pointer_pointer(_: *mut *mut VaListImpl) -> usize; + fn test_va_list_by_pointer(_: *mut VaList) -> usize; + fn test_va_list_by_pointer_pointer(_: *mut *mut VaList) -> usize; } #[unsafe(no_mangle)] @@ -165,7 +162,7 @@ extern "C" fn run_test_variadic() -> usize { #[unsafe(no_mangle)] extern "C" fn run_test_va_list_by_value() -> usize { unsafe extern "C" fn helper(mut ap: ...) -> usize { - unsafe { test_va_list_by_value(ap.as_va_list()) } + unsafe { test_va_list_by_value(ap) } } unsafe { helper(1 as c_longlong, 2 as c_int, 3 as c_longlong) } diff --git a/tests/ui/abi/variadic-ffi.rs b/tests/ui/abi/variadic-ffi.rs index dfdbff33264b..3ffa0bea0ecf 100644 --- a/tests/ui/abi/variadic-ffi.rs +++ b/tests/ui/abi/variadic-ffi.rs @@ -10,37 +10,45 @@ fn rust_interesting_average(_: u64, ...) -> f64; fn rust_valist_interesting_average(_: u64, _: VaList) -> f64; + + fn rust_va_list_next_i32(_: *mut VaList<'_>) -> i32; } -pub unsafe extern "C" fn test_valist_forward(n: u64, mut ap: ...) -> f64 { - rust_valist_interesting_average(n, ap.as_va_list()) +pub unsafe extern "C" fn test_valist_forward(n: u64, ap: ...) -> f64 { + rust_valist_interesting_average(n, ap) } -pub unsafe extern "C-unwind" fn c_unwind_can_forward(n: u64, mut ap: ...) -> f64 { - rust_valist_interesting_average(n, ap.as_va_list()) +pub unsafe extern "C-unwind" fn c_unwind_can_forward(n: u64, ap: ...) -> f64 { + rust_valist_interesting_average(n, ap) } pub unsafe extern "C" fn test_va_copy(_: u64, mut ap: ...) { - let mut ap2 = ap.clone(); - assert_eq!(rust_valist_interesting_average(2, ap2.as_va_list()) as i64, 30); + let ap2 = ap.clone(); + assert_eq!(rust_valist_interesting_average(2, ap2) as i64, 30); // Advance one pair in the copy before checking let mut ap2 = ap.clone(); let _ = ap2.arg::(); let _ = ap2.arg::(); - assert_eq!(rust_valist_interesting_average(2, ap2.as_va_list()) as i64, 50); + assert_eq!(rust_valist_interesting_average(2, ap2) as i64, 50); // Advance one pair in the original let _ = ap.arg::(); let _ = ap.arg::(); - let mut ap2 = ap.clone(); - assert_eq!(rust_valist_interesting_average(2, ap2.as_va_list()) as i64, 50); + let ap2 = ap.clone(); + assert_eq!(rust_valist_interesting_average(2, ap2) as i64, 50); let mut ap2 = ap.clone(); let _ = ap2.arg::(); let _ = ap2.arg::(); - assert_eq!(rust_valist_interesting_average(2, ap2.as_va_list()) as i64, 70); + assert_eq!(rust_valist_interesting_average(2, ap2) as i64, 70); +} + +pub unsafe extern "C" fn test_ref(mut ap: ...) { + assert_eq!(rust_va_list_next_i32(&mut ap), 2); + assert_eq!(rust_va_list_next_i32(&mut ap), 4); + assert_eq!(rust_va_list_next_i32(&mut ap), 8); } pub fn main() { @@ -85,4 +93,8 @@ unsafe fn call(fp: unsafe extern "C" fn(u64, ...) -> f64) { unsafe { test_va_copy(4, 10i64, 10f64, 20i64, 20f64, 30i64, 30f64, 40i64, 40f64); } + + unsafe { + test_ref(2, 4, 8); + } } diff --git a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr new file mode 100644 index 000000000000..fe11c4288618 --- /dev/null +++ b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr @@ -0,0 +1,80 @@ +error: fn_abi_of(take_va_list) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(32 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(32 bytes), + pointee_align: Some( + Align(8 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:26:1 + | +LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/c-variadic/pass-by-value-abi.rs b/tests/ui/c-variadic/pass-by-value-abi.rs new file mode 100644 index 000000000000..b65442af2472 --- /dev/null +++ b/tests/ui/c-variadic/pass-by-value-abi.rs @@ -0,0 +1,46 @@ +//@ check-fail +//@ normalize-stderr: "randomization_seed: \d+" -> "randomization_seed: $$SEED" +//@ normalize-stderr: "valid_range: 0\.\.=\d+" -> "valid_range: 0..=$$MAX" +//@ normalize-stderr: "memory_index: \[[^\]]+\]" -> "memory_index: $$MEMORY_INDEX" +//@ normalize-stderr: "offsets: \[[^\]]+\]" -> "offsets: $$OFFSETS" +//@ revisions: x86_64 aarch64 win +//@ compile-flags: -O +//@ [x86_64] only-x86_64 +//@ [x86_64] ignore-windows +//@ [x86_64] ignore-uefi +//@ [aarch64] only-aarch64 +//@ [aarch64] ignore-windows +//@ [aarch64] ignore-apple +//@ [aarch64] ignore-uefi +// Windows dosen't use `#[rustc_pass_indirectly_in_non_rustic_abis]` and is tested in CI, so is here +// for comparison. +//@ [win] only-windows + +#![feature(rustc_attrs, c_variadic)] +#![crate_type = "lib"] + +// Can't use `minicore` here as this is testing the implementation in `core::ffi` specifically. +use std::ffi::VaList; + +#[rustc_abi(debug)] +pub extern "C" fn take_va_list(_: VaList<'_>) {} +//~^ ERROR fn_abi_of(take_va_list) = FnAbi { +//[x86_64]~^^ ERROR mode: Indirect { +//[x86_64]~^^^ ERROR on_stack: false, +//[aarch64]~^^^^ ERROR mode: Indirect { +//[aarch64]~^^^^^ ERROR on_stack: false, +//[win]~^^^^^^ ERROR mode: Direct( + +#[cfg(all(target_arch = "x86_64", not(windows)))] +#[rustc_abi(debug)] +pub extern "sysv64" fn take_va_list_sysv64(_: VaList<'_>) {} +//[x86_64]~^ ERROR fn_abi_of(take_va_list_sysv64) = FnAbi { +//[x86_64]~^^ ERROR mode: Indirect { +//[x86_64]~^^^ ERROR on_stack: false, + +#[cfg(all(target_arch = "x86_64", not(windows)))] +#[rustc_abi(debug)] +pub extern "win64" fn take_va_list_win64(_: VaList<'_>) {} +//[x86_64]~^ ERROR: fn_abi_of(take_va_list_win64) = FnAbi { +//[x86_64]~^^ ERROR mode: Indirect { +//[x86_64]~^^^ ERROR on_stack: false, diff --git a/tests/ui/c-variadic/pass-by-value-abi.win.stderr b/tests/ui/c-variadic/pass-by-value-abi.win.stderr new file mode 100644 index 000000000000..e84430859e02 --- /dev/null +++ b/tests/ui/c-variadic/pass-by-value-abi.win.stderr @@ -0,0 +1,83 @@ +error: fn_abi_of(take_va_list) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(8 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Scalar( + Initialized { + value: Pointer( + AddressSpace( + 0, + ), + ), + valid_range: 0..=$MAX, + }, + ), + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Direct( + ArgAttributes { + regular: NoUndef, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: None, + }, + ), + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:26:1 + | +LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr new file mode 100644 index 000000000000..73f1ccd5992a --- /dev/null +++ b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr @@ -0,0 +1,240 @@ +error: fn_abi_of(take_va_list) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(24 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(24 bytes), + pointee_align: Some( + Align(8 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:26:1 + | +LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: fn_abi_of(take_va_list_sysv64) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(24 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(24 bytes), + pointee_align: Some( + Align(8 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: X86( + SysV64, + ), + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:36:1 + | +LL | pub extern "sysv64" fn take_va_list_sysv64(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: fn_abi_of(take_va_list_win64) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: VaList<'_>, + layout: Layout { + size: Size(24 bytes), + align: AbiAlign { + abi: Align(8 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: $OFFSETS, + memory_index: $MEMORY_INDEX, + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(8 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(24 bytes), + pointee_align: Some( + Align(8 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: X86( + Win64, + ), + can_unwind: false, + } + --> $DIR/pass-by-value-abi.rs:43:1 + | +LL | pub extern "win64" fn take_va_list_win64(_: VaList<'_>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/c-variadic/variadic-ffi-4.rs b/tests/ui/c-variadic/variadic-ffi-4.rs index 806403794225..d9e2e617ce3a 100644 --- a/tests/ui/c-variadic/variadic-ffi-4.rs +++ b/tests/ui/c-variadic/variadic-ffi-4.rs @@ -2,37 +2,30 @@ #![no_std] #![feature(c_variadic)] -use core::ffi::{VaList, VaListImpl}; +use core::ffi::VaList; -pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f> { +pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaList<'f> { ap //~^ ERROR: lifetime may not live long enough - //~| ERROR: lifetime may not live long enough } -pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaListImpl<'static> { +pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> { ap //~ ERROR: lifetime may not live long enough } -pub unsafe extern "C" fn no_escape2(_: usize, ap: ...) { - let _ = ap.with_copy(|ap| ap); //~ ERROR: lifetime may not live long enough -} - -pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { +pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) { *ap0 = ap1; //~^ ERROR: lifetime may not live long enough - //~| ERROR: lifetime may not live long enough } -pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { +pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { ap0 = &mut ap1; //~^ ERROR: `ap1` does not live long enough //~| ERROR: lifetime may not live long enough //~| ERROR: lifetime may not live long enough } -pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { +pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaList, mut ap1: ...) { *ap0 = ap1.clone(); //~^ ERROR: lifetime may not live long enough - //~| ERROR: lifetime may not live long enough } diff --git a/tests/ui/c-variadic/variadic-ffi-4.stderr b/tests/ui/c-variadic/variadic-ffi-4.stderr index fc9f8036083a..a230bb6f5861 100644 --- a/tests/ui/c-variadic/variadic-ffi-4.stderr +++ b/tests/ui/c-variadic/variadic-ffi-4.stderr @@ -1,113 +1,64 @@ error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:8:5 | -LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f> { - | -- -- has type `VaListImpl<'1>` - | | - | lifetime `'f` defined here -LL | ap - | ^^ function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'f` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance - -error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:8:5 - | -LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaListImpl<'f> { - | -- -- has type `VaListImpl<'1>` +LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaList<'f> { + | -- -- has type `VaList<'1>` | | | lifetime `'f` defined here LL | ap | ^^ function was supposed to return data with lifetime `'f` but it is returning data with lifetime `'1` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:14:5 + --> $DIR/variadic-ffi-4.rs:13:5 | -LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaListImpl<'static> { - | -- has type `VaListImpl<'1>` +LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> { + | -- has type `VaList<'1>` LL | ap | ^^ returning this value requires that `'1` must outlive `'static` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:18:31 + --> $DIR/variadic-ffi-4.rs:17:5 | -LL | let _ = ap.with_copy(|ap| ap); - | --- ^^ returning this value requires that `'1` must outlive `'2` - | | | - | | return type of closure is VaList<'2, '_> - | has type `VaList<'1, '_>` - -error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:22:5 - | -LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` +LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | ------- ------- has type `VaList<'1>` | | - | has type `&mut VaListImpl<'1>` + | has type `&mut VaList<'2>` LL | *ap0 = ap1; | ^^^^ assignment requires that `'1` must outlive `'2` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:22:5 | -LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` +LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaListImpl<'1>` -LL | *ap0 = ap1; - | ^^^^ assignment requires that `'2` must outlive `'1` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance - -error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:28:5 - | -LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` - | | - | has type `&mut VaListImpl<'1>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2` | - = note: requirement occurs because of a mutable reference to `VaListImpl<'_>` + = note: requirement occurs because of a mutable reference to `VaList<'_>` = note: mutable references are invariant over their type parameter = help: see for more information about variance error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:28:5 + --> $DIR/variadic-ffi-4.rs:22:5 | -LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` +LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaListImpl<'1>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'2` must outlive `'1` | - = note: requirement occurs because of a mutable reference to `VaListImpl<'_>` + = note: requirement occurs because of a mutable reference to `VaList<'_>` = note: mutable references are invariant over their type parameter = help: see for more information about variance error[E0597]: `ap1` does not live long enough - --> $DIR/variadic-ffi-4.rs:28:11 + --> $DIR/variadic-ffi-4.rs:22:11 | -LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | - ------- binding `ap1` declared here +LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | - ------- binding `ap1` declared here | | | let's call the lifetime of this reference `'3` LL | ap0 = &mut ap1; @@ -120,33 +71,15 @@ LL | } | - `ap1` dropped here while still borrowed error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:35:5 + --> $DIR/variadic-ffi-4.rs:29:5 | -LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` +LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaList, mut ap1: ...) { + | ------- ------- has type `VaList<'1>` | | - | has type `&mut VaListImpl<'1>` + | has type `&mut VaList<'2>` LL | *ap0 = ap1.clone(); - | ^^^^ assignment requires that `'2` must outlive `'1` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance + | ^^^^ assignment requires that `'1` must outlive `'2` -error: lifetime may not live long enough - --> $DIR/variadic-ffi-4.rs:35:12 - | -LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaListImpl, mut ap1: ...) { - | ------- ------- has type `VaListImpl<'2>` - | | - | has type `&mut VaListImpl<'1>` -LL | *ap0 = ap1.clone(); - | ^^^^^^^^^^^ argument requires that `'1` must outlive `'2` - | - = note: requirement occurs because of the type `VaListImpl<'_>`, which makes the generic argument `'_` invariant - = note: the struct `VaListImpl<'f>` is invariant over the parameter `'f` - = help: see for more information about variance - -error: aborting due to 11 previous errors +error: aborting due to 7 previous errors For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/parser/macro/macro-dotdotdot-may-not-begin-a-type.rs b/tests/ui/parser/macro/macro-dotdotdot-may-not-begin-a-type.rs index 8be99f22d2ee..b29f6915ae3d 100644 --- a/tests/ui/parser/macro/macro-dotdotdot-may-not-begin-a-type.rs +++ b/tests/ui/parser/macro/macro-dotdotdot-may-not-begin-a-type.rs @@ -1,4 +1,4 @@ -// A bare `...` represents `CVarArgs` (`VaListImpl<'_>`) in function argument type +// A bare `...` represents `CVarArgs` (`VaList<'_>`) in function argument type // position without being a proper type syntactically. // This test ensures that we do not regress certain MBE calls would we ever promote // `...` to a proper type syntactically. diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs index 025c0e3ecaca..6f61425a8bd6 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs @@ -32,12 +32,12 @@ extern "C" fn f3_3(_: ..., x: isize) {} const unsafe extern "C" fn f4_1(x: isize, _: ...) {} //~^ ERROR functions cannot be both `const` and C-variadic -//~| ERROR destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +//~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time const extern "C" fn f4_2(x: isize, _: ...) {} //~^ ERROR functions cannot be both `const` and C-variadic //~| ERROR functions with a C variable argument list must be unsafe -//~| ERROR destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +//~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} //~^ ERROR functions cannot be both `const` and C-variadic @@ -65,7 +65,7 @@ fn i_f4(_: ..., x: isize, _: ...) {} const fn i_f5(x: isize, _: ...) {} //~^ ERROR `...` is not supported for non-extern functions //~| ERROR functions cannot be both `const` and C-variadic - //~| ERROR destructor of `VaListImpl<'_>` cannot be evaluated at compile-time + //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time } trait T { diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr index 0e02d4434233..318015737fa1 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr @@ -236,7 +236,7 @@ error: `...` must be the last argument of a C-variadic function LL | fn t_f6(_: ..., x: isize); | ^^^^^^ -error[E0493]: destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:33:43 | LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} @@ -244,7 +244,7 @@ LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} | | | the destructor for this type cannot be evaluated in constant functions -error[E0493]: destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} @@ -252,7 +252,7 @@ LL | const extern "C" fn f4_2(x: isize, _: ...) {} | | | the destructor for this type cannot be evaluated in constant functions -error[E0493]: destructor of `VaListImpl<'_>` cannot be evaluated at compile-time +error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 | LL | const fn i_f5(x: isize, _: ...) {} diff --git a/tests/ui/thir-print/c-variadic.stdout b/tests/ui/thir-print/c-variadic.stdout index d64b2b9aa9d1..f1905e04f72b 100644 --- a/tests/ui/thir-print/c-variadic.stdout +++ b/tests/ui/thir-print/c-variadic.stdout @@ -16,13 +16,13 @@ params: [ ) } Param { - ty: std::ffi::VaListImpl<'{erased}> + ty: std::ffi::VaList<'{erased}> ty_span: None self_kind: None hir_id: Some(HirId(DefId(0:3 ~ c_variadic[a5de]::foo).3)) param: Some( Pat: { - ty: std::ffi::VaListImpl<'{erased}> + ty: std::ffi::VaList<'{erased}> span: $DIR/c-variadic.rs:7:34: 7:37 (#0) kind: PatKind { Missing From 376b284521061803356c7c711c194b31be027ce4 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 4 Nov 2025 19:32:06 +0100 Subject: [PATCH 16/26] document `VaList` ABI for more targets --- library/core/src/ffi/va_list.rs | 57 ++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 449e62ac00d3..4c59ea0cc532 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -8,11 +8,29 @@ use crate::intrinsics::{va_arg, va_copy}; use crate::marker::PhantomCovariantLifetime; -// Most targets explicitly specify the layout of `va_list`, this layout is matched here. -// For `va_list`s which are single-element array in C (and therefore experience array-to-pointer -// decay when passed as arguments in C), the `VaList` struct is annotated with -// `#[rustc_pass_indirectly_in_non_rustic_abis]`. This ensures that the compiler uses the correct -// ABI for functions like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly. +// There are currently three flavors of how a C `va_list` is implemented for +// targets that Rust supports: +// +// - `va_list` is an opaque pointer +// - `va_list` is a struct +// - `va_list` is a single-element array, containing a struct +// +// The opaque pointer approach is the simplest to implement: the pointer just +// points to an array of arguments on the caller's stack. +// +// The struct and single-element array variants are more complex, but +// potentially more efficient because the additional state makes it +// possible to pass variadic arguments via registers. +// +// The Rust `VaList` type is ABI-compatible with the C `va_list`. +// The struct and pointer cases straightforwardly map to their Rust equivalents, +// but the single-element array case is special: in C, this type is subject to +// array-to-pointer decay. +// +// The `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute is used to match +// the pointer decay behavior in Rust, while otherwise matching Rust semantics. +// This attribute ensures that the compiler uses the correct ABI for functions +// like `extern "C" fn takes_va_list(va: VaList<'_>)` by passing `va` indirectly. crate::cfg_select! { all( target_arch = "aarch64", @@ -20,8 +38,9 @@ not(target_os = "uefi"), not(windows), ) => { - /// AArch64 ABI implementation of a `va_list`. See the - /// [AArch64 Procedure Call Standard] for more details. + /// AArch64 ABI implementation of a `va_list`. + /// + /// See the [AArch64 Procedure Call Standard] for more details. /// /// [AArch64 Procedure Call Standard]: /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf @@ -37,6 +56,12 @@ struct VaListInner { } all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)) => { /// PowerPC ABI implementation of a `va_list`. + /// + /// See the [LLVM source] and [GCC header] for more details. + /// + /// [LLVM source]: + /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/PowerPC/PPCISelLowering.cpp#L4089-L4111 + /// [GCC header]: https://web.mit.edu/darwin/src/modules/gcc/gcc/ginclude/va-ppc.h #[repr(C)] #[derive(Debug)] #[rustc_pass_indirectly_in_non_rustic_abis] @@ -50,6 +75,11 @@ struct VaListInner { } target_arch = "s390x" => { /// s390x ABI implementation of a `va_list`. + /// + /// See the [S/390x ELF Application Binary Interface Supplement] for more details. + /// + /// [S/390x ELF Application Binary Interface Supplement]: + /// https://docs.google.com/gview?embedded=true&url=https://github.com/IBM/s390x-abi/releases/download/v1.7/lzsabi_s390x.pdf #[repr(C)] #[derive(Debug)] #[rustc_pass_indirectly_in_non_rustic_abis] @@ -61,7 +91,12 @@ struct VaListInner { } } all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)) => { - /// x86_64 ABI implementation of a `va_list`. + /// x86_64 System V ABI implementation of a `va_list`. + /// + /// See the [System V AMD64 ABI] for more details. + /// + /// [System V AMD64 ABI]: + /// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf #[repr(C)] #[derive(Debug)] #[rustc_pass_indirectly_in_non_rustic_abis] @@ -74,6 +109,11 @@ struct VaListInner { } target_arch = "xtensa" => { /// Xtensa ABI implementation of a `va_list`. + /// + /// See the [LLVM source] for more details. + /// + /// [LLVM source]: + /// https://github.com/llvm/llvm-project/blob/af9a4263a1a209953a1d339ef781a954e31268ff/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp#L1211-L1215 #[repr(C)] #[derive(Debug)] #[rustc_pass_indirectly_in_non_rustic_abis] @@ -88,6 +128,7 @@ struct VaListInner { // // - apple aarch64 (see https://github.com/rust-lang/rust/pull/56599) // - windows + // - powerpc64 & powerpc64le // - uefi // - any other target for which we don't specify the `VaListInner` above // From 08979745c9bc87b38aa3a952593a08db25a83711 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 4 Dec 2025 11:13:24 +0100 Subject: [PATCH 17/26] split out `va_list` forwarding into its own test --- tests/codegen-llvm/cffi/c-variadic-va_list.rs | 36 +++++++++++++++++++ tests/codegen-llvm/cffi/c-variadic.rs | 22 ------------ 2 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 tests/codegen-llvm/cffi/c-variadic-va_list.rs diff --git a/tests/codegen-llvm/cffi/c-variadic-va_list.rs b/tests/codegen-llvm/cffi/c-variadic-va_list.rs new file mode 100644 index 000000000000..e2652491f421 --- /dev/null +++ b/tests/codegen-llvm/cffi/c-variadic-va_list.rs @@ -0,0 +1,36 @@ +//@ needs-unwind +//@ compile-flags: -Copt-level=3 +//@ min-llvm-version: 21 + +#![crate_type = "lib"] +#![feature(c_variadic)] +#![no_std] +use core::ffi::VaList; + +// Ensure that we do not remove the `va_list` passed to the foreign function when +// removing the "spoofed" `VaList` that is used by Rust defined C-variadics. + +extern "C" { + fn foreign_c_variadic_1(_: VaList, ...); +} + +// CHECK-LABEL: use_foreign_c_variadic_1_0 +pub unsafe extern "C" fn use_foreign_c_variadic_1_0(ap: VaList) { + // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap) + foreign_c_variadic_1(ap); +} + +// CHECK-LABEL: use_foreign_c_variadic_1_1 +pub unsafe extern "C" fn use_foreign_c_variadic_1_1(ap: VaList) { + // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap, i32 noundef 42) + foreign_c_variadic_1(ap, 42i32); +} +pub unsafe extern "C" fn use_foreign_c_variadic_1_2(ap: VaList) { + // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap, i32 noundef 2, i32 noundef 42) + foreign_c_variadic_1(ap, 2i32, 42i32); +} + +pub unsafe extern "C" fn use_foreign_c_variadic_1_3(ap: VaList) { + // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %ap, i32 noundef 2, i32 noundef 42, i32 noundef 0) + foreign_c_variadic_1(ap, 2i32, 42i32, 0i32); +} diff --git a/tests/codegen-llvm/cffi/c-variadic.rs b/tests/codegen-llvm/cffi/c-variadic.rs index 3ce421eb4a5c..7a2e2ba5047e 100644 --- a/tests/codegen-llvm/cffi/c-variadic.rs +++ b/tests/codegen-llvm/cffi/c-variadic.rs @@ -9,7 +9,6 @@ extern "C" { fn foreign_c_variadic_0(_: i32, ...); - fn foreign_c_variadic_1(_: VaList, ...); } pub unsafe extern "C" fn use_foreign_c_variadic_0() { @@ -24,27 +23,6 @@ foreign_c_variadic_0(0, 42i32, 1024i32, 0i32); } -// Ensure that we do not remove the `va_list` passed to the foreign function when -// removing the "spoofed" `VaList` that is used by Rust defined C-variadics. -pub unsafe extern "C" fn use_foreign_c_variadic_1_0(ap: VaList) { - // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %0) - foreign_c_variadic_1(ap); -} - -pub unsafe extern "C" fn use_foreign_c_variadic_1_1(ap: VaList) { - // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %0, [[PARAM]] 42) - foreign_c_variadic_1(ap, 42i32); -} -pub unsafe extern "C" fn use_foreign_c_variadic_1_2(ap: VaList) { - // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %0, [[PARAM]] 2, [[PARAM]] 42) - foreign_c_variadic_1(ap, 2i32, 42i32); -} - -pub unsafe extern "C" fn use_foreign_c_variadic_1_3(ap: VaList) { - // CHECK: call void ({{.*}}, ...) @foreign_c_variadic_1({{.*}} %0, [[PARAM]] 2, [[PARAM]] 42, [[PARAM]] 0) - foreign_c_variadic_1(ap, 2i32, 42i32, 0i32); -} - // Ensure that `va_start` and `va_end` are properly injected. #[no_mangle] pub unsafe extern "C" fn c_variadic(n: i32, mut ap: ...) -> i32 { From 3d0f5f2f88678b894ebafad599852e129229e5d8 Mon Sep 17 00:00:00 2001 From: reddevilmidzy Date: Fri, 5 Dec 2025 00:39:55 +0900 Subject: [PATCH 18/26] Add regression test for 141845 --- ...solution-with-inherent-associated-types.rs | 13 +++++++++++++ ...tion-with-inherent-associated-types.stderr | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/ui/const-generics/mgca/resolution-with-inherent-associated-types.rs create mode 100644 tests/ui/const-generics/mgca/resolution-with-inherent-associated-types.stderr diff --git a/tests/ui/const-generics/mgca/resolution-with-inherent-associated-types.rs b/tests/ui/const-generics/mgca/resolution-with-inherent-associated-types.rs new file mode 100644 index 000000000000..6a8d291e3cc0 --- /dev/null +++ b/tests/ui/const-generics/mgca/resolution-with-inherent-associated-types.rs @@ -0,0 +1,13 @@ +//! Regression test for +//! Checks const resolution stability when using inherent associated types +//! and generic const arguments. + +//@compile-flags: --crate-type=lib +#![expect(incomplete_features)] +#![feature(inherent_associated_types, min_generic_const_args)] +trait Trait {} + +struct Struct; + +type Alias = Struct<{ Struct::N }>; +//~^ ERROR: missing generics for struct `Struct` [E0107] diff --git a/tests/ui/const-generics/mgca/resolution-with-inherent-associated-types.stderr b/tests/ui/const-generics/mgca/resolution-with-inherent-associated-types.stderr new file mode 100644 index 000000000000..13e73dafbea6 --- /dev/null +++ b/tests/ui/const-generics/mgca/resolution-with-inherent-associated-types.stderr @@ -0,0 +1,19 @@ +error[E0107]: missing generics for struct `Struct` + --> $DIR/resolution-with-inherent-associated-types.rs:12:33 + | +LL | type Alias = Struct<{ Struct::N }>; + | ^^^^^^ expected 1 generic argument + | +note: struct defined here, with 1 generic parameter: `N` + --> $DIR/resolution-with-inherent-associated-types.rs:10:8 + | +LL | struct Struct; + | ^^^^^^ -------------- +help: add missing generic argument + | +LL | type Alias = Struct<{ Struct::N }>; + | +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0107`. From ad73972e999b79ea4e17d997676bdb8327288c77 Mon Sep 17 00:00:00 2001 From: Alina Sbirlea Date: Thu, 4 Dec 2025 20:21:49 +0000 Subject: [PATCH 19/26] Fix for LLVM22 making lowering decisions dependent on RuntimeLibraryInfo. LLVM reference commit: https://github.com/llvm/llvm-project/commit/04c81a99735c04b2018eeb687e74f9860e1d0e1b. --- compiler/rustc_codegen_llvm/src/back/write.rs | 2 +- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 1 + compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp | 12 +++++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index c0c01b80372f..b131de1df8ba 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -75,7 +75,7 @@ fn write_output_file<'ll>( let result = unsafe { let pm = llvm::LLVMCreatePassManager(); llvm::LLVMAddAnalysisPasses(target, pm); - llvm::LLVMRustAddLibraryInfo(pm, m, no_builtins); + llvm::LLVMRustAddLibraryInfo(target, pm, m, no_builtins); llvm::LLVMRustWriteOutputFile( target, pm, diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 53b9a2bda894..c5cbc92ae772 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2379,6 +2379,7 @@ pub(crate) fn LLVMRustCreateTargetMachine( ) -> *mut TargetMachine; pub(crate) fn LLVMRustAddLibraryInfo<'a>( + T: &TargetMachine, PM: &PassManager<'a>, M: &'a Module, DisableSimplifyLibCalls: bool, diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index 143cc9479089..714ba0f177a8 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -6,6 +6,9 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/Analysis/Lint.h" #include "llvm/Analysis/TargetLibraryInfo.h" +#if LLVM_VERSION_GE(22, 0) +#include "llvm/Analysis/RuntimeLibcallInfo.h" +#endif #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/Bitcode/BitcodeWriterPass.h" #include "llvm/CodeGen/CommandFlags.h" @@ -379,13 +382,20 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine( // Unfortunately, the LLVM C API doesn't provide a way to create the // TargetLibraryInfo pass, so we use this method to do so. -extern "C" void LLVMRustAddLibraryInfo(LLVMPassManagerRef PMR, LLVMModuleRef M, +extern "C" void LLVMRustAddLibraryInfo(LLVMTargetMachineRef T, + LLVMPassManagerRef PMR, LLVMModuleRef M, bool DisableSimplifyLibCalls) { auto TargetTriple = Triple(unwrap(M)->getTargetTriple()); + TargetOptions *Options = &unwrap(T)->Options; auto TLII = TargetLibraryInfoImpl(TargetTriple); if (DisableSimplifyLibCalls) TLII.disableAllFunctions(); unwrap(PMR)->add(new TargetLibraryInfoWrapperPass(TLII)); +#if LLVM_VERSION_GE(22, 0) + unwrap(PMR)->add(new RuntimeLibraryInfoWrapper( + TargetTriple, Options->ExceptionModel, Options->FloatABIType, + Options->EABIVersion, Options->MCOptions.ABIName, Options->VecLib)); +#endif } extern "C" void LLVMRustSetLLVMOptions(int Argc, char **Argv) { From 84ff44c74933374a941f0c8c49e8381d830c3193 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 5 Dec 2025 13:44:03 +1100 Subject: [PATCH 20/26] Add perma-unstable `--print=backend-has-zstd` for use by compiletest Tests for `-Zdebuginfo-compression=zstd` need to be skipped if LLVM was built without support for zstd compression. Currently, compiletest relies on messy and fragile heuristics to detect whether the compiler's LLVM was built with zstd support. But the compiler itself already knows whether LLVM has zstd or not, so it's easier for compiletest to just ask the compiler. --- compiler/rustc_codegen_llvm/src/lib.rs | 4 + compiler/rustc_driver_impl/src/lib.rs | 1 + .../rustc_session/src/config/print_request.rs | 3 + src/tools/compiletest/src/common.rs | 6 +- src/tools/compiletest/src/directives.rs | 101 ------------------ src/tools/compiletest/src/directives/needs.rs | 23 +++- .../help-diff.diff | 2 +- .../unstable-invalid-print-request-help.err | 2 +- tests/run-make/rustc-help/help-v.stdout | 2 +- tests/run-make/rustc-help/help.stdout | 2 +- .../print-without-arg.stderr | 2 +- tests/ui/invalid-compile-flags/print.stderr | 2 +- .../backend-has-zstd-unstable.rs | 8 ++ .../backend-has-zstd-unstable.stderr | 2 + .../ui/print-request/print-lints-help.stderr | 2 +- 15 files changed, 50 insertions(+), 112 deletions(-) create mode 100644 tests/ui/print-request/backend-has-zstd-unstable.rs create mode 100644 tests/ui/print-request/backend-has-zstd-unstable.stderr diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 1b65a133d58c..8c0c0afcc1dd 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -257,6 +257,10 @@ fn print(&self, req: &PrintRequest, out: &mut String, sess: &Session) { } writeln!(out).unwrap(); } + PrintKind::BackendHasZstd => { + let has_zstd = llvm::LLVMRustLLVMHasZstdCompression(); + writeln!(out, "{has_zstd}").unwrap(); + } PrintKind::CodeModels => { writeln!(out, "Available code models:").unwrap(); for name in &["tiny", "small", "kernel", "medium", "large"] { diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 9a3d7cc506cf..0853f638509f 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -799,6 +799,7 @@ fn print_crate_info( println_info!("{}", calling_conventions.join("\n")); } RelocationModels + | BackendHasZstd | CodeModels | TlsModels | TargetCPUs diff --git a/compiler/rustc_session/src/config/print_request.rs b/compiler/rustc_session/src/config/print_request.rs index b8111fbc17f8..395f7a4fab71 100644 --- a/compiler/rustc_session/src/config/print_request.rs +++ b/compiler/rustc_session/src/config/print_request.rs @@ -22,6 +22,7 @@ pub struct PrintRequest { pub enum PrintKind { // tidy-alphabetical-start AllTargetSpecsJson, + BackendHasZstd, CallingConventions, Cfg, CheckCfg, @@ -59,6 +60,7 @@ fn name(self) -> &'static str { match self { // tidy-alphabetical-start AllTargetSpecsJson => "all-target-specs-json", + BackendHasZstd => "backend-has-zstd", CallingConventions => "calling-conventions", Cfg => "cfg", CheckCfg => "check-cfg", @@ -111,6 +113,7 @@ fn is_stable(self) -> bool { // Unstable values: AllTargetSpecsJson => false, + BackendHasZstd => false, // (perma-unstable, for use by compiletest) CheckCfg => false, CrateRootLintLevels => false, SupportedCrateTypes => false, diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index eb1fc55a2624..d8472691afdf 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -1096,7 +1096,11 @@ fn supported_crate_types(config: &Config) -> HashSet { crate_types } -fn query_rustc_output(config: &Config, args: &[&str], envs: HashMap) -> String { +pub(crate) fn query_rustc_output( + config: &Config, + args: &[&str], + envs: HashMap, +) -> String { let query_rustc_path = config.query_rustc_path.as_deref().unwrap_or(&config.rustc_path); let mut command = Command::new(query_rustc_path); diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index fed30415de56..c154886ebcde 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -885,107 +885,6 @@ pub fn extract_llvm_version_from_binary(binary_path: &str) -> Option { None } -/// For tests using the `needs-llvm-zstd` directive: -/// - for local LLVM builds, try to find the static zstd library in the llvm-config system libs. -/// - for `download-ci-llvm`, see if `lld` was built with zstd support. -pub fn llvm_has_libzstd(config: &Config) -> bool { - // Strategy 1: works for local builds but not with `download-ci-llvm`. - // - // We check whether `llvm-config` returns the zstd library. Bootstrap's `llvm.libzstd` will only - // ask to statically link it when building LLVM, so we only check if the list of system libs - // contains a path to that static lib, and that it exists. - // - // See compiler/rustc_llvm/build.rs for more details and similar expectations. - fn is_zstd_in_config(llvm_bin_dir: &Utf8Path) -> Option<()> { - let llvm_config_path = llvm_bin_dir.join("llvm-config"); - let output = Command::new(llvm_config_path).arg("--system-libs").output().ok()?; - assert!(output.status.success(), "running llvm-config --system-libs failed"); - - let libs = String::from_utf8(output.stdout).ok()?; - for lib in libs.split_whitespace() { - if lib.ends_with("libzstd.a") && Utf8Path::new(lib).exists() { - return Some(()); - } - } - - None - } - - // Strategy 2: `download-ci-llvm`'s `llvm-config --system-libs` will not return any libs to - // use. - // - // The CI artifacts also don't contain the bootstrap config used to build them: otherwise we - // could have looked at the `llvm.libzstd` config. - // - // We infer whether `LLVM_ENABLE_ZSTD` was used to build LLVM as a byproduct of testing whether - // `lld` supports it. If not, an error will be emitted: "LLVM was not built with - // LLVM_ENABLE_ZSTD or did not find zstd at build time". - #[cfg(unix)] - fn is_lld_built_with_zstd(llvm_bin_dir: &Utf8Path) -> Option<()> { - let lld_path = llvm_bin_dir.join("lld"); - if lld_path.exists() { - // We can't call `lld` as-is, it expects to be invoked by a compiler driver using a - // different name. Prepare a temporary symlink to do that. - let lld_symlink_path = llvm_bin_dir.join("ld.lld"); - if !lld_symlink_path.exists() { - std::os::unix::fs::symlink(lld_path, &lld_symlink_path).ok()?; - } - - // Run `lld` with a zstd flag. We expect this command to always error here, we don't - // want to link actual files and don't pass any. - let output = Command::new(&lld_symlink_path) - .arg("--compress-debug-sections=zstd") - .output() - .ok()?; - assert!(!output.status.success()); - - // Look for a specific error caused by LLVM not being built with zstd support. We could - // also look for the "no input files" message, indicating the zstd flag was accepted. - let stderr = String::from_utf8(output.stderr).ok()?; - let zstd_available = !stderr.contains("LLVM was not built with LLVM_ENABLE_ZSTD"); - - // We don't particularly need to clean the link up (so the previous commands could fail - // in theory but won't in practice), but we can try. - std::fs::remove_file(lld_symlink_path).ok()?; - - if zstd_available { - return Some(()); - } - } - - None - } - - #[cfg(not(unix))] - fn is_lld_built_with_zstd(_llvm_bin_dir: &Utf8Path) -> Option<()> { - None - } - - if let Some(llvm_bin_dir) = &config.llvm_bin_dir { - // Strategy 1: for local LLVM builds. - if is_zstd_in_config(llvm_bin_dir).is_some() { - return true; - } - - // Strategy 2: for LLVM artifacts built on CI via `download-ci-llvm`. - // - // It doesn't work for cases where the artifacts don't contain the linker, but it's - // best-effort: CI has `llvm.libzstd` and `lld` enabled on the x64 linux artifacts, so it - // will at least work there. - // - // If this can be improved and expanded to less common cases in the future, it should. - if config.target == "x86_64-unknown-linux-gnu" - && config.host == config.target - && is_lld_built_with_zstd(llvm_bin_dir).is_some() - { - return true; - } - } - - // Otherwise, all hope is lost. - false -} - /// Takes a directive of the form `" [- ]"`, returns the numeric representation /// of `` and `` as tuple: `(, )`. /// diff --git a/src/tools/compiletest/src/directives/needs.rs b/src/tools/compiletest/src/directives/needs.rs index b54400621092..208e96166021 100644 --- a/src/tools/compiletest/src/directives/needs.rs +++ b/src/tools/compiletest/src/directives/needs.rs @@ -1,5 +1,7 @@ -use crate::common::{Config, KNOWN_CRATE_TYPES, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer}; -use crate::directives::{DirectiveLine, IgnoreDecision, llvm_has_libzstd}; +use crate::common::{ + Config, KNOWN_CRATE_TYPES, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer, query_rustc_output, +}; +use crate::directives::{DirectiveLine, IgnoreDecision}; pub(super) fn handle_needs( cache: &CachedNeedsConditions, @@ -377,7 +379,7 @@ pub(super) fn load(config: &Config) -> Self { .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" }) .exists(), - llvm_zstd: llvm_has_libzstd(&config), + llvm_zstd: llvm_has_zstd(&config), dlltool: find_dlltool(&config), symlinks: has_symlinks(), } @@ -428,3 +430,18 @@ fn has_symlinks() -> bool { fn has_symlinks() -> bool { true } + +fn llvm_has_zstd(config: &Config) -> bool { + // The compiler already knows whether LLVM was built with zstd or not, + // so compiletest can just ask the compiler. + let output = query_rustc_output( + config, + &["-Zunstable-options", "--print=backend-has-zstd"], + Default::default(), + ); + match output.trim() { + "true" => true, + "false" => false, + _ => panic!("unexpected output from `--print=backend-has-zstd`: {output:?}"), + } +} diff --git a/tests/run-make/print-request-help-stable-unstable/help-diff.diff b/tests/run-make/print-request-help-stable-unstable/help-diff.diff index 044302a19a01..e382a2478271 100644 --- a/tests/run-make/print-request-help-stable-unstable/help-diff.diff +++ b/tests/run-make/print-request-help-stable-unstable/help-diff.diff @@ -2,6 +2,6 @@ error: unknown print request: `xxx` | - = help: valid print requests are: `calling-conventions`, `cfg`, `code-models`, `crate-name`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `tls-models` -+ = help: valid print requests are: `all-target-specs-json`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models` ++ = help: valid print requests are: `all-target-specs-json`, `backend-has-zstd`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models` = help: for more information, see the rustc book: https://doc.rust-lang.org/rustc/command-line-arguments.html#--print-print-compiler-information diff --git a/tests/run-make/print-request-help-stable-unstable/unstable-invalid-print-request-help.err b/tests/run-make/print-request-help-stable-unstable/unstable-invalid-print-request-help.err index cc6c3c909b36..70764ea13aa8 100644 --- a/tests/run-make/print-request-help-stable-unstable/unstable-invalid-print-request-help.err +++ b/tests/run-make/print-request-help-stable-unstable/unstable-invalid-print-request-help.err @@ -1,5 +1,5 @@ error: unknown print request: `xxx` | - = help: valid print requests are: `all-target-specs-json`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models` + = help: valid print requests are: `all-target-specs-json`, `backend-has-zstd`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models` = help: for more information, see the rustc book: https://doc.rust-lang.org/rustc/command-line-arguments.html#--print-print-compiler-information diff --git a/tests/run-make/rustc-help/help-v.stdout b/tests/run-make/rustc-help/help-v.stdout index cd161c51ee3b..c41cb5e3bde8 100644 --- a/tests/run-make/rustc-help/help-v.stdout +++ b/tests/run-make/rustc-help/help-v.stdout @@ -43,7 +43,7 @@ Options: --print [=] Compiler information to print on stdout (or to a file) INFO may be one of - . + . -g Equivalent to -C debuginfo=2 -O Equivalent to -C opt-level=3 -o Write output to FILENAME diff --git a/tests/run-make/rustc-help/help.stdout b/tests/run-make/rustc-help/help.stdout index 74ec083bdee7..5e13a900484d 100644 --- a/tests/run-make/rustc-help/help.stdout +++ b/tests/run-make/rustc-help/help.stdout @@ -43,7 +43,7 @@ Options: --print [=] Compiler information to print on stdout (or to a file) INFO may be one of - . + . -g Equivalent to -C debuginfo=2 -O Equivalent to -C opt-level=3 -o Write output to FILENAME diff --git a/tests/ui/invalid-compile-flags/print-without-arg.stderr b/tests/ui/invalid-compile-flags/print-without-arg.stderr index 4163d4e06022..ff9669614360 100644 --- a/tests/ui/invalid-compile-flags/print-without-arg.stderr +++ b/tests/ui/invalid-compile-flags/print-without-arg.stderr @@ -3,5 +3,5 @@ error: Argument to option 'print' missing --print [=] Compiler information to print on stdout (or to a file) INFO may be one of - . + . diff --git a/tests/ui/invalid-compile-flags/print.stderr b/tests/ui/invalid-compile-flags/print.stderr index e8adbfd87d76..e2521ebf26a4 100644 --- a/tests/ui/invalid-compile-flags/print.stderr +++ b/tests/ui/invalid-compile-flags/print.stderr @@ -1,5 +1,5 @@ error: unknown print request: `yyyy` | - = help: valid print requests are: `all-target-specs-json`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models` + = help: valid print requests are: `all-target-specs-json`, `backend-has-zstd`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models` = help: for more information, see the rustc book: https://doc.rust-lang.org/rustc/command-line-arguments.html#--print-print-compiler-information diff --git a/tests/ui/print-request/backend-has-zstd-unstable.rs b/tests/ui/print-request/backend-has-zstd-unstable.rs new file mode 100644 index 000000000000..cfed4af0c794 --- /dev/null +++ b/tests/ui/print-request/backend-has-zstd-unstable.rs @@ -0,0 +1,8 @@ +//! Check that `--print=backend-has-zstd` is unstable. +//! +//! That print value is intended for use by compiletest, and should probably +//! never be stabilized in this form. + +//@ compile-flags: --print=backend-has-zstd + +//~? ERROR: the `-Z unstable-options` flag must also be passed diff --git a/tests/ui/print-request/backend-has-zstd-unstable.stderr b/tests/ui/print-request/backend-has-zstd-unstable.stderr new file mode 100644 index 000000000000..c7b5aa68d873 --- /dev/null +++ b/tests/ui/print-request/backend-has-zstd-unstable.stderr @@ -0,0 +1,2 @@ +error: the `-Z unstable-options` flag must also be passed to enable the `backend-has-zstd` print option + diff --git a/tests/ui/print-request/print-lints-help.stderr b/tests/ui/print-request/print-lints-help.stderr index 297a3aa79e1f..d39c6326e318 100644 --- a/tests/ui/print-request/print-lints-help.stderr +++ b/tests/ui/print-request/print-lints-help.stderr @@ -1,6 +1,6 @@ error: unknown print request: `lints` | - = help: valid print requests are: `all-target-specs-json`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models` + = help: valid print requests are: `all-target-specs-json`, `backend-has-zstd`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models` = help: use `-Whelp` to print a list of lints = help: for more information, see the rustc book: https://doc.rust-lang.org/rustc/command-line-arguments.html#--print-print-compiler-information From f040a1a9152b6bd080692750183810cb0c37290b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 5 Dec 2025 08:42:24 +0100 Subject: [PATCH 21/26] interpret: test SNaN handling of float min/max and update comments --- .../src/interpret/intrinsics.rs | 52 +++++++++++++------ .../src/interpret/intrinsics/simd.rs | 12 ++--- src/tools/miri/tests/pass/float.rs | 52 ++++++++----------- 3 files changed, 64 insertions(+), 52 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index fa5041daa69e..a7a3bbebed5f 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -40,18 +40,20 @@ pub(crate) enum MinMax { /// In particular, `-0.0` is considered smaller than `+0.0` and /// if either input is NaN, the result is NaN. Minimum, - /// The IEEE-2008 `minNum` operation - see `f32::min` etc. + /// The IEEE-2008 `minNum` operation with the SNaN handling of the + /// IEEE-2019 `minimumNumber` operation - see `f32::min` etc. /// In particular, if the inputs are `-0.0` and `+0.0`, the result is non-deterministic, - /// and if one argument is NaN, the other one is returned. - MinNum, + /// and if one argument is NaN (quiet or signaling), the other one is returned. + MinimumNumber, /// The IEEE-2019 `maximum` operation - see `f32::maximum` etc. /// In particular, `-0.0` is considered smaller than `+0.0` and /// if either input is NaN, the result is NaN. Maximum, - /// The IEEE-2008 `maxNum` operation - see `f32::max` etc. + /// The IEEE-2008 `maxNum` operation with the SNaN handling of the + /// IEEE-2019 `maximumNumber` operation - see `f32::max` etc. /// In particular, if the inputs are `-0.0` and `+0.0`, the result is non-deterministic, - /// and if one argument is NaN, the other one is returned. - MaxNum, + /// and if one argument is NaN (quiet or signaling), the other one is returned. + MaximumNumber, } /// Directly returns an `Allocation` containing an absolute path representation of the given type. @@ -524,10 +526,18 @@ pub fn eval_intrinsic( self.write_scalar(Scalar::from_target_usize(align.bytes(), self), dest)?; } - sym::minnumf16 => self.float_minmax_intrinsic::(args, MinMax::MinNum, dest)?, - sym::minnumf32 => self.float_minmax_intrinsic::(args, MinMax::MinNum, dest)?, - sym::minnumf64 => self.float_minmax_intrinsic::(args, MinMax::MinNum, dest)?, - sym::minnumf128 => self.float_minmax_intrinsic::(args, MinMax::MinNum, dest)?, + sym::minnumf16 => { + self.float_minmax_intrinsic::(args, MinMax::MinimumNumber, dest)? + } + sym::minnumf32 => { + self.float_minmax_intrinsic::(args, MinMax::MinimumNumber, dest)? + } + sym::minnumf64 => { + self.float_minmax_intrinsic::(args, MinMax::MinimumNumber, dest)? + } + sym::minnumf128 => { + self.float_minmax_intrinsic::(args, MinMax::MinimumNumber, dest)? + } sym::minimumf16 => self.float_minmax_intrinsic::(args, MinMax::Minimum, dest)?, sym::minimumf32 => { @@ -538,10 +548,18 @@ pub fn eval_intrinsic( } sym::minimumf128 => self.float_minmax_intrinsic::(args, MinMax::Minimum, dest)?, - sym::maxnumf16 => self.float_minmax_intrinsic::(args, MinMax::MaxNum, dest)?, - sym::maxnumf32 => self.float_minmax_intrinsic::(args, MinMax::MaxNum, dest)?, - sym::maxnumf64 => self.float_minmax_intrinsic::(args, MinMax::MaxNum, dest)?, - sym::maxnumf128 => self.float_minmax_intrinsic::(args, MinMax::MaxNum, dest)?, + sym::maxnumf16 => { + self.float_minmax_intrinsic::(args, MinMax::MaximumNumber, dest)? + } + sym::maxnumf32 => { + self.float_minmax_intrinsic::(args, MinMax::MaximumNumber, dest)? + } + sym::maxnumf64 => { + self.float_minmax_intrinsic::(args, MinMax::MaximumNumber, dest)? + } + sym::maxnumf128 => { + self.float_minmax_intrinsic::(args, MinMax::MaximumNumber, dest)? + } sym::maximumf16 => self.float_minmax_intrinsic::(args, MinMax::Maximum, dest)?, sym::maximumf32 => { @@ -966,16 +984,16 @@ fn float_minmax( { let a: F = a.to_float()?; let b: F = b.to_float()?; - let res = if matches!(op, MinMax::MinNum | MinMax::MaxNum) && a == b { + let res = if matches!(op, MinMax::MinimumNumber | MinMax::MaximumNumber) && a == b { // They are definitely not NaN (those are never equal), but they could be `+0` and `-0`. // Let the machine decide which one to return. M::equal_float_min_max(self, a, b) } else { let result = match op { MinMax::Minimum => a.minimum(b), - MinMax::MinNum => a.min(b), + MinMax::MinimumNumber => a.min(b), MinMax::Maximum => a.maximum(b), - MinMax::MaxNum => a.max(b), + MinMax::MaximumNumber => a.max(b), }; self.adjust_nan(result, &[a, b]) }; diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs index bae423840ee1..20de47683122 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs @@ -202,8 +202,8 @@ enum Op { sym::simd_le => Op::MirOp(BinOp::Le), sym::simd_gt => Op::MirOp(BinOp::Gt), sym::simd_ge => Op::MirOp(BinOp::Ge), - sym::simd_fmax => Op::FMinMax(MinMax::MaxNum), - sym::simd_fmin => Op::FMinMax(MinMax::MinNum), + sym::simd_fmax => Op::FMinMax(MinMax::MaximumNumber), + sym::simd_fmin => Op::FMinMax(MinMax::MinimumNumber), sym::simd_saturating_add => Op::SaturatingOp(BinOp::Add), sym::simd_saturating_sub => Op::SaturatingOp(BinOp::Sub), sym::simd_arith_offset => Op::WrappingOffset, @@ -295,8 +295,8 @@ enum Op { sym::simd_reduce_xor => Op::MirOp(BinOp::BitXor), sym::simd_reduce_any => Op::MirOpBool(BinOp::BitOr), sym::simd_reduce_all => Op::MirOpBool(BinOp::BitAnd), - sym::simd_reduce_max => Op::MinMax(MinMax::MaxNum), - sym::simd_reduce_min => Op::MinMax(MinMax::MinNum), + sym::simd_reduce_max => Op::MinMax(MinMax::MaximumNumber), + sym::simd_reduce_min => Op::MinMax(MinMax::MinimumNumber), _ => unreachable!(), }; @@ -320,8 +320,8 @@ enum Op { } else { // Just boring integers, no NaNs to worry about. let mirop = match mmop { - MinMax::MinNum | MinMax::Minimum => BinOp::Le, - MinMax::MaxNum | MinMax::Maximum => BinOp::Ge, + MinMax::MinimumNumber | MinMax::Minimum => BinOp::Le, + MinMax::MaximumNumber | MinMax::Maximum => BinOp::Ge, }; if self.binary_op(mirop, &res, &op)?.to_scalar().to_bool()? { res diff --git a/src/tools/miri/tests/pass/float.rs b/src/tools/miri/tests/pass/float.rs index 7b23518d73da..a74a66d5455a 100644 --- a/src/tools/miri/tests/pass/float.rs +++ b/src/tools/miri/tests/pass/float.rs @@ -48,29 +48,15 @@ macro_rules! assert_approx_eq { }; } -/// From IEEE 754 a Signaling NaN for single precision has the following representation: -/// ``` -/// s | 1111 1111 | 0x..x -/// ```` -/// Were at least one `x` is a 1. -/// -/// This sNaN has the following representation and is used for testing purposes.: -/// ``` -/// 0 | 1111111 | 01..0 -/// ``` -const SNAN_F32: f32 = f32::from_bits(0x7fa00000); - -/// From IEEE 754 a Signaling NaN for double precision has the following representation: -/// ``` -/// s | 1111 1111 111 | 0x..x -/// ```` -/// Were at least one `x` is a 1. -/// -/// This sNaN has the following representation and is used for testing purposes.: -/// ``` -/// 0 | 1111 1111 111 | 01..0 -/// ``` -const SNAN_F64: f64 = f64::from_bits(0x7ff4000000000000); +/// We turn the quiet NaN f*::NAN into a signaling one by flipping the first (most significant) +/// two bits of the mantissa. For this we have to shift by `MANTISSA_DIGITS-3` because: +/// we subtract 1 as the actual mantissa is 1 bit smaller, and 2 more as that's the width +/// if the value we are shifting. +const F16_SNAN: f16 = f16::from_bits(f16::NAN.to_bits() ^ (0b11 << (f16::MANTISSA_DIGITS - 3))); +const F32_SNAN: f32 = f32::from_bits(f32::NAN.to_bits() ^ (0b11 << (f32::MANTISSA_DIGITS - 3))); +const F64_SNAN: f64 = f64::from_bits(f64::NAN.to_bits() ^ (0b11 << (f64::MANTISSA_DIGITS - 3))); +const F128_SNAN: f128 = + f128::from_bits(f128::NAN.to_bits() ^ (0b11 << (f128::MANTISSA_DIGITS - 3))); fn main() { basic(); @@ -757,6 +743,8 @@ fn ops() { assert_eq(f16::NAN.max(-9.0), -9.0); assert_eq((9.0_f16).min(f16::NAN), 9.0); assert_eq((-9.0_f16).max(f16::NAN), -9.0); + assert_eq(F16_SNAN.min(9.0), 9.0); + assert_eq((-9.0_f16).max(F16_SNAN), -9.0); // f32 min/max assert_eq((1.0 as f32).max(-1.0), 1.0); @@ -765,6 +753,8 @@ fn ops() { assert_eq(f32::NAN.max(-9.0), -9.0); assert_eq((9.0 as f32).min(f32::NAN), 9.0); assert_eq((-9.0 as f32).max(f32::NAN), -9.0); + assert_eq(F32_SNAN.min(9.0), 9.0); + assert_eq((-9.0_f32).max(F32_SNAN), -9.0); // f64 min/max assert_eq((1.0 as f64).max(-1.0), 1.0); @@ -773,6 +763,8 @@ fn ops() { assert_eq(f64::NAN.max(-9.0), -9.0); assert_eq((9.0 as f64).min(f64::NAN), 9.0); assert_eq((-9.0 as f64).max(f64::NAN), -9.0); + assert_eq(F64_SNAN.min(9.0), 9.0); + assert_eq((-9.0_f64).max(F64_SNAN), -9.0); // f128 min/max assert_eq((1.0_f128).max(-1.0), 1.0); @@ -781,6 +773,8 @@ fn ops() { assert_eq(f128::NAN.max(-9.0), -9.0); assert_eq((9.0_f128).min(f128::NAN), 9.0); assert_eq((-9.0_f128).max(f128::NAN), -9.0); + assert_eq(F128_SNAN.min(9.0), 9.0); + assert_eq((-9.0_f128).max(F128_SNAN), -9.0); // f16 copysign assert_eq(3.5_f16.copysign(0.42), 3.5_f16); @@ -1548,15 +1542,15 @@ fn test_operations_f128(a: f128, b: f128) { test_operations_f128(25., 18.); // SNaN^0 = (1 | NaN) - check_nondet(|| f32::powf(SNAN_F32, 0.0).is_nan()); - check_nondet(|| f64::powf(SNAN_F64, 0.0).is_nan()); + check_nondet(|| f32::powf(F32_SNAN, 0.0).is_nan()); + check_nondet(|| f64::powf(F64_SNAN, 0.0).is_nan()); // 1^SNaN = (1 | NaN) - check_nondet(|| f32::powf(1.0, SNAN_F32).is_nan()); - check_nondet(|| f64::powf(1.0, SNAN_F64).is_nan()); + check_nondet(|| f32::powf(1.0, F32_SNAN).is_nan()); + check_nondet(|| f64::powf(1.0, F64_SNAN).is_nan()); // same as powf (keep it consistent): // x^SNaN = (1 | NaN) - check_nondet(|| f32::powi(SNAN_F32, 0).is_nan()); - check_nondet(|| f64::powi(SNAN_F64, 0).is_nan()); + check_nondet(|| f32::powi(F32_SNAN, 0).is_nan()); + check_nondet(|| f64::powi(F64_SNAN, 0).is_nan()); } From d6db951b22dc3deb48a637aca54c5bb0a3db4795 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 5 Dec 2025 12:14:03 +0100 Subject: [PATCH 22/26] only run `pass-by-value-abi` on 64-bit windows the 32-bit variant differs only in the alignment/size, and I can't test it locally --- tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr | 2 +- tests/ui/c-variadic/pass-by-value-abi.rs | 3 ++- tests/ui/c-variadic/pass-by-value-abi.win.stderr | 2 +- tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr index fe11c4288618..a86b28d98ecc 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr @@ -71,7 +71,7 @@ error: fn_abi_of(take_va_list) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/pass-by-value-abi.rs:26:1 + --> $DIR/pass-by-value-abi.rs:27:1 | LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/c-variadic/pass-by-value-abi.rs b/tests/ui/c-variadic/pass-by-value-abi.rs index b65442af2472..1e3935067cc2 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.rs +++ b/tests/ui/c-variadic/pass-by-value-abi.rs @@ -12,9 +12,10 @@ //@ [aarch64] ignore-windows //@ [aarch64] ignore-apple //@ [aarch64] ignore-uefi -// Windows dosen't use `#[rustc_pass_indirectly_in_non_rustic_abis]` and is tested in CI, so is here +// Windows doesn't use `#[rustc_pass_indirectly_in_non_rustic_abis]` and is tested in CI, so is here // for comparison. //@ [win] only-windows +//@ [win] only-x86_64 #![feature(rustc_attrs, c_variadic)] #![crate_type = "lib"] diff --git a/tests/ui/c-variadic/pass-by-value-abi.win.stderr b/tests/ui/c-variadic/pass-by-value-abi.win.stderr index e84430859e02..8ff93961f667 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.win.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.win.stderr @@ -74,7 +74,7 @@ error: fn_abi_of(take_va_list) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/pass-by-value-abi.rs:26:1 + --> $DIR/pass-by-value-abi.rs:27:1 | LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr index 73f1ccd5992a..aafe89d59e33 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr @@ -71,7 +71,7 @@ error: fn_abi_of(take_va_list) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/pass-by-value-abi.rs:26:1 + --> $DIR/pass-by-value-abi.rs:27:1 | LL | pub extern "C" fn take_va_list(_: VaList<'_>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -151,7 +151,7 @@ error: fn_abi_of(take_va_list_sysv64) = FnAbi { ), can_unwind: false, } - --> $DIR/pass-by-value-abi.rs:36:1 + --> $DIR/pass-by-value-abi.rs:37:1 | LL | pub extern "sysv64" fn take_va_list_sysv64(_: VaList<'_>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -231,7 +231,7 @@ error: fn_abi_of(take_va_list_win64) = FnAbi { ), can_unwind: false, } - --> $DIR/pass-by-value-abi.rs:43:1 + --> $DIR/pass-by-value-abi.rs:44:1 | LL | pub extern "win64" fn take_va_list_win64(_: VaList<'_>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 3e717121a1c96ed9717ebac84ecbeac021dabb21 Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Tue, 2 Dec 2025 15:30:06 +0300 Subject: [PATCH 23/26] Generate error delegation body when delegation is not resolved --- compiler/rustc_ast_lowering/src/delegation.rs | 40 +++++++++++++++++-- .../ice-line-bounds-issue-148732.rs | 1 + .../ice-line-bounds-issue-148732.stderr | 18 +++++++-- .../ui/delegation/unused-import-ice-144594.rs | 13 ++++++ .../unused-import-ice-144594.stderr | 15 +++++++ 5 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 tests/ui/delegation/unused-import-ice-144594.rs create mode 100644 tests/ui/delegation/unused-import-ice-144594.stderr diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index 9bfcd232221b..e6e88eff2d5b 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -96,7 +96,7 @@ pub(crate) fn lower_delegation( let generics = self.lower_delegation_generics(span); DelegationResults { body_id, sig, ident, generics } } - Err(err) => self.generate_delegation_error(err, span), + Err(err) => self.generate_delegation_error(err, span, delegation), } } @@ -404,6 +404,7 @@ fn generate_delegation_error( &mut self, err: ErrorGuaranteed, span: Span, + delegation: &Delegation, ) -> DelegationResults<'hir> { let generics = self.lower_delegation_generics(span); @@ -418,8 +419,41 @@ fn generate_delegation_error( let header = self.generate_header_error(); let sig = hir::FnSig { decl, header, span }; - let ident = Ident::dummy(); - let body_id = self.lower_body(|this| (&[], this.mk_expr(hir::ExprKind::Err(err), span))); + let ident = self.lower_ident(delegation.ident); + + let body_id = self.lower_body(|this| { + let body_expr = match delegation.body.as_ref() { + Some(box block) => { + // Generates a block when we failed to resolve delegation, where a target expression is its only statement, + // thus there will be no ICEs on further stages of analysis (see #144594) + + // As we generate a void function we want to convert target expression to statement to avoid additional + // errors, such as mismatched return type + let stmts = this.arena.alloc_from_iter([hir::Stmt { + hir_id: this.next_id(), + kind: rustc_hir::StmtKind::Semi( + this.arena.alloc(this.lower_target_expr(block)), + ), + span, + }]); + + let block = this.arena.alloc(hir::Block { + stmts, + expr: None, + hir_id: this.next_id(), + rules: hir::BlockCheckMode::DefaultBlock, + span, + targeted_by_break: false, + }); + + hir::ExprKind::Block(block, None) + } + None => hir::ExprKind::Err(err), + }; + + (&[], this.mk_expr(body_expr, span)) + }); + DelegationResults { ident, generics, body_id, sig } } diff --git a/tests/ui/delegation/ice-line-bounds-issue-148732.rs b/tests/ui/delegation/ice-line-bounds-issue-148732.rs index 699e7d86f258..e44c78476021 100644 --- a/tests/ui/delegation/ice-line-bounds-issue-148732.rs +++ b/tests/ui/delegation/ice-line-bounds-issue-148732.rs @@ -3,6 +3,7 @@ //~| ERROR functions delegation is not yet fully implemented dbg!(b); //~^ ERROR missing lifetime specifier + //~| ERROR `fn() {b}` doesn't implement `Debug` } fn main() {} diff --git a/tests/ui/delegation/ice-line-bounds-issue-148732.stderr b/tests/ui/delegation/ice-line-bounds-issue-148732.stderr index c65b1560818d..1f43ec335448 100644 --- a/tests/ui/delegation/ice-line-bounds-issue-148732.stderr +++ b/tests/ui/delegation/ice-line-bounds-issue-148732.stderr @@ -20,7 +20,7 @@ LL | / reuse a as b { LL | | LL | | LL | | dbg!(b); -LL | | +... | LL | | } | |_^ | @@ -28,7 +28,19 @@ LL | | } = help: add `#![feature(fn_delegation)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 3 previous errors +error[E0277]: `fn() {b}` doesn't implement `Debug` + --> $DIR/ice-line-bounds-issue-148732.rs:4:5 + | +LL | reuse a as b { + | - consider calling this function +... +LL | dbg!(b); + | ^^^^^^^ the trait `Debug` is not implemented for fn item `fn() {b}` + | + = help: use parentheses to call this function: `b()` + = note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info) -Some errors have detailed explanations: E0106, E0425, E0658. +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0106, E0277, E0425, E0658. For more information about an error, try `rustc --explain E0106`. diff --git a/tests/ui/delegation/unused-import-ice-144594.rs b/tests/ui/delegation/unused-import-ice-144594.rs new file mode 100644 index 000000000000..1d064a4c978f --- /dev/null +++ b/tests/ui/delegation/unused-import-ice-144594.rs @@ -0,0 +1,13 @@ +#![allow(incomplete_features)] +#![feature(fn_delegation)] + +reuse a as b { + //~^ ERROR cannot find function `a` in this scope [E0425] + || { + use std::ops::Add; + x.add + //~^ ERROR cannot find value `x` in this scope [E0425] + } +} + +fn main() {} diff --git a/tests/ui/delegation/unused-import-ice-144594.stderr b/tests/ui/delegation/unused-import-ice-144594.stderr new file mode 100644 index 000000000000..1939380235ee --- /dev/null +++ b/tests/ui/delegation/unused-import-ice-144594.stderr @@ -0,0 +1,15 @@ +error[E0425]: cannot find function `a` in this scope + --> $DIR/unused-import-ice-144594.rs:4:7 + | +LL | reuse a as b { + | ^ not found in this scope + +error[E0425]: cannot find value `x` in this scope + --> $DIR/unused-import-ice-144594.rs:8:9 + | +LL | x.add + | ^ not found in this scope + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0425`. From e6b6de40c23893fbce3e37f708175cb4f7e6e1ae Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 5 Dec 2025 19:33:24 +0000 Subject: [PATCH 24/26] bump version number --- src/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version b/src/version index 95784efddbc4..8db4a57b3d02 100644 --- a/src/version +++ b/src/version @@ -1 +1 @@ -1.93.0 +1.94.0 From de3100e56077343ae5491655b6162615ae573cfb Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Sat, 6 Dec 2025 04:53:34 +0000 Subject: [PATCH 25/26] Prepare for merging from rust-lang/rust This updates the rust-version file to 36b2369c91d32c2659887ed6fe3d570640f44fd2. --- 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 45bf94eb4de4..b6a1415f1834 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -864339abf952f07098dd82610256338520167d4a +36b2369c91d32c2659887ed6fe3d570640f44fd2 From 77825a264367ac4eafd4011e58a743411b687432 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 6 Dec 2025 10:15:15 +0100 Subject: [PATCH 26/26] ubuntu riscv64 is broken even with extensive retries --- src/tools/miri/.github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index d6c702848494..4eeba3022892 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -31,13 +31,13 @@ jobs: os: ubuntu-24.04-arm multiarch: armhf gcc_cross: arm-linux-gnueabihf - - host_target: riscv64gc-unknown-linux-gnu - os: ubuntu-latest - multiarch: riscv64 - gcc_cross: riscv64-linux-gnu - qemu: true # Ubuntu mirrors are not reliable enough for these architectures # (see ). + # - host_target: riscv64gc-unknown-linux-gnu + # os: ubuntu-latest + # multiarch: riscv64 + # gcc_cross: riscv64-linux-gnu + # qemu: true # - host_target: s390x-unknown-linux-gnu # os: ubuntu-latest # multiarch: s390x