From b9ad3a99755d771bd2eedce799d4954a6a4b8545 Mon Sep 17 00:00:00 2001 From: joboet Date: Wed, 11 Feb 2026 15:25:26 +0100 Subject: [PATCH 1/6] core: drop unmapped ZSTs in array `map` --- library/core/src/array/drain.rs | 69 ++++++++++++++++++++++---------- library/coretests/tests/array.rs | 27 +++++++++++++ library/coretests/tests/lib.rs | 1 + 3 files changed, 75 insertions(+), 22 deletions(-) diff --git a/library/core/src/array/drain.rs b/library/core/src/array/drain.rs index 1c6137191324c..54fc080e24528 100644 --- a/library/core/src/array/drain.rs +++ b/library/core/src/array/drain.rs @@ -1,8 +1,8 @@ use crate::marker::{Destruct, PhantomData}; -use crate::mem::{ManuallyDrop, SizedTypeProperties, conjure_zst}; -use crate::ptr::{NonNull, drop_in_place, from_raw_parts_mut, null_mut}; +use crate::mem::{ManuallyDrop, SizedTypeProperties, conjure_zst, transmute}; +use crate::ptr::{NonNull, drop_in_place, from_raw_parts_mut, without_provenance_mut}; -impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> { +impl<'l, 'f, T, U, F: FnMut(T) -> U> Drain<'l, 'f, T, F> { /// This function returns a function that lets you index the given array in const. /// As implemented it can optimize better than iterators, and can be constified. /// It acts like a sort of guard (owns the array) and iterator combined, which can be implemented @@ -14,9 +14,11 @@ impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> { /// This will also not actually store the array. /// /// SAFETY: must only be called `N` times. Thou shalt not drop the array either. - // FIXME(const-hack): this is a hack for `let guard = Guard(array); |i| f(guard[i])`. #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] - pub(super) const unsafe fn new(array: &'l mut ManuallyDrop<[T; N]>, f: &'f mut F) -> Self { + pub(super) const unsafe fn new( + array: &'l mut ManuallyDrop<[T; N]>, + f: &'f mut F, + ) -> Self { // dont drop the array, transfers "ownership" to Self let ptr: NonNull = NonNull::from_mut(array).cast(); // SAFETY: @@ -24,8 +26,9 @@ impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> { // at the end of `slice`. `end` will never be dereferenced, only checked // for direct pointer equality with `ptr` to check if the drainer is done. unsafe { - let end = if T::IS_ZST { null_mut() } else { ptr.as_ptr().add(N) }; - Self { ptr, end, f, l: PhantomData } + let end_or_len = + if T::IS_ZST { without_provenance_mut(N) } else { ptr.as_ptr().add(N) }; + Self { ptr, end_or_len, f, l: PhantomData } } } } @@ -33,8 +36,8 @@ impl<'l, 'f, T, U, const N: usize, F: FnMut(T) -> U> Drain<'l, 'f, T, N, F> { /// See [`Drain::new`]; this is our fake iterator. #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] #[unstable(feature = "array_try_map", issue = "79711")] -pub(super) struct Drain<'l, 'f, T, const N: usize, F> { - // FIXME(const-hack): This is essentially a slice::IterMut<'static>, replace when possible. +pub(super) struct Drain<'l, 'f, T, F> { + // FIXME(const-hack): This is a slice::IterMut<'l>, replace when possible. /// The pointer to the next element to return, or the past-the-end location /// if the drainer is empty. /// @@ -42,16 +45,16 @@ pub(super) struct Drain<'l, 'f, T, const N: usize, F> { /// As we "own" this array, we dont need to store any lifetime. ptr: NonNull, /// For non-ZSTs, the non-null pointer to the past-the-end element. - /// For ZSTs, this is null. - end: *mut T, + /// For ZSTs, this is the number of unprocessed items. + end_or_len: *mut T, f: &'f mut F, - l: PhantomData<&'l mut [T; N]>, + l: PhantomData<&'l mut [T]>, } #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] #[unstable(feature = "array_try_map", issue = "79711")] -impl const FnOnce<(usize,)> for &mut Drain<'_, '_, T, N, F> +impl const FnOnce<(usize,)> for &mut Drain<'_, '_, T, F> where F: [const] FnMut(T) -> U, { @@ -64,7 +67,7 @@ where } #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] #[unstable(feature = "array_try_map", issue = "79711")] -impl const FnMut<(usize,)> for &mut Drain<'_, '_, T, N, F> +impl const FnMut<(usize,)> for &mut Drain<'_, '_, T, F> where F: [const] FnMut(T) -> U, { @@ -74,6 +77,16 @@ where (_ /* ignore argument */,): (usize,), ) -> Self::Output { if T::IS_ZST { + #[expect(ptr_to_integer_transmute_in_consts)] + // SAFETY: + // This is equivalent to `self.end_or_len.addr`, but that's not + // available in `const`. `self.end_or_len` doesn't have provenance, + // so transmuting is fine. + let len = unsafe { transmute::<*mut T, usize>(self.end_or_len) }; + // SAFETY: + // The caller guarantees that this is never called more than N times + // (see `Drain::new`), hence this cannot underflow. + self.end_or_len = without_provenance_mut(unsafe { len.unchecked_sub(1) }); // its UB to call this more than N times, so returning more ZSTs is valid. // SAFETY: its a ZST? we conjur. (self.f)(unsafe { conjure_zst::() }) @@ -89,20 +102,32 @@ where } #[rustc_const_unstable(feature = "array_try_map", issue = "79711")] #[unstable(feature = "array_try_map", issue = "79711")] -impl const Drop for Drain<'_, '_, T, N, F> { +impl const Drop for Drain<'_, '_, T, F> { fn drop(&mut self) { - if !T::IS_ZST { + let slice = if T::IS_ZST { + from_raw_parts_mut::<[T]>( + self.ptr.as_ptr(), + #[expect(ptr_to_integer_transmute_in_consts)] + // SAFETY: + // This is equivalent to `self.end_or_len.addr`, but that's not + // available in `const`. `self.end_or_len` doesn't have provenance, + // so transmuting is fine. + unsafe { + transmute::<*mut T, usize>(self.end_or_len) + }, + ) + } else { // SAFETY: we cant read more than N elements - let slice = unsafe { + unsafe { from_raw_parts_mut::<[T]>( self.ptr.as_ptr(), // SAFETY: `start <= end` - self.end.offset_from_unsigned(self.ptr.as_ptr()), + self.end_or_len.offset_from_unsigned(self.ptr.as_ptr()), ) - }; + } + }; - // SAFETY: By the type invariant, we're allowed to drop all these. (we own it, after all) - unsafe { drop_in_place(slice) } - } + // SAFETY: By the type invariant, we're allowed to drop all these. (we own it, after all) + unsafe { drop_in_place(slice) } } } diff --git a/library/coretests/tests/array.rs b/library/coretests/tests/array.rs index 2b4429092e98b..f68204de700a0 100644 --- a/library/coretests/tests/array.rs +++ b/library/coretests/tests/array.rs @@ -1,6 +1,8 @@ +use core::cell::Cell; use core::num::NonZero; use core::sync::atomic::{AtomicUsize, Ordering}; use core::{array, assert_eq}; +use std::sync::ReentrantLock; #[test] fn array_from_ref() { @@ -718,6 +720,31 @@ fn array_map_drops_unmapped_elements_on_panic() { } } +#[cfg(not(panic = "abort"))] +#[test] +fn array_map_drops_unmapped_zst_elements_on_panic() { + static DROPPED: ReentrantLock> = ReentrantLock::new(Cell::new(0)); + + struct ZstDrop; + impl Drop for ZstDrop { + fn drop(&mut self) { + DROPPED.lock().update(|x| x + 1); + } + } + + let dropped = DROPPED.lock(); + dropped.set(0); + let array = [const { ZstDrop }; 5]; + let success = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let _ = array.map(|x| { + drop(x); + assert_eq!(dropped.get(), 1); + }); + })); + assert!(success.is_err()); + assert_eq!(dropped.get(), 5); +} + // This covers the `PartialEq::<[T]>::eq` impl for `[T; N]` when it returns false. #[test] fn array_eq() { diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 5923328655524..bac4f3b228007 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -99,6 +99,7 @@ #![feature(pointer_is_aligned_to)] #![feature(portable_simd)] #![feature(ptr_metadata)] +#![feature(reentrant_lock)] #![feature(result_option_map_or_default)] #![feature(signed_bigint_helpers)] #![feature(slice_from_ptr_range)] From cafe02e1e353c1ddb72b4981d3beb97d4f99eba3 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 27 Apr 2026 09:47:23 +0200 Subject: [PATCH 2/6] Add `first_span` to doc attribute --- compiler/rustc_attr_parsing/src/attributes/doc.rs | 3 +++ compiler/rustc_hir/src/attrs/data_structures.rs | 4 ++++ compiler/rustc_passes/src/check_attr.rs | 1 + src/librustdoc/clean/mod.rs | 1 + src/librustdoc/json/conversions.rs | 1 + 5 files changed, 10 insertions(+) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 5d07478b152ee..60f469990d51a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -701,6 +701,9 @@ impl DocParser { for i in items.mixed() { match i { MetaItemOrLitParser::MetaItemParser(mip) => { + if self.nb_doc_attrs == 0 { + self.attribute.first_span = cx.attr_span; + } self.nb_doc_attrs += 1; self.parse_single_doc_attr_item(cx, mip); } diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 6304b830f6ed7..bab86a25e3cbb 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -544,6 +544,8 @@ pub struct CfgHideShow { #[derive(Clone, Debug, Default, HashStable_Generic, Decodable, PrintAttribute)] pub struct DocAttribute { + pub first_span: Span, + pub aliases: FxIndexMap, pub hidden: Option, // Because we need to emit the error if there is more than one `inline` attribute on an item @@ -581,6 +583,7 @@ pub struct DocAttribute { impl rustc_serialize::Encodable for DocAttribute { fn encode(&self, encoder: &mut E) { let DocAttribute { + first_span, aliases, hidden, inline, @@ -603,6 +606,7 @@ impl rustc_serialize::Encodable for DocAttribute test_attrs, no_crate_inject, } = self; + rustc_serialize::Encodable::::encode(first_span, encoder); rustc_serialize::Encodable::::encode(aliases, encoder); rustc_serialize::Encodable::::encode(hidden, encoder); diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index ebf0f06f7173d..34c2ec54e8fc9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1027,6 +1027,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { /// [`check_doc_inline`]: Self::check_doc_inline fn check_doc_attrs(&self, attr: &DocAttribute, hir_id: HirId, target: Target) { let DocAttribute { + first_span: _, aliases, // valid pretty much anywhere, not checked here? // FIXME: should we? diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 81e348f96e569..3f604c1b7bb8b 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2780,6 +2780,7 @@ fn add_without_unwanted_attributes<'hir>( hir::Attribute::Parsed(AttributeKind::Doc(box d)) => { // Remove attributes from `normal` that should not be inherited by `use` re-export. let DocAttribute { + first_span: _, aliases, hidden, inline, diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 5d1f4778f1c52..44749fd4c0ec6 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -960,6 +960,7 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) } let DocAttribute { + first_span: _, aliases, hidden, inline, From 71ef1fafe02bd7b62bbdbecf050977d037c90eb3 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 27 Apr 2026 09:48:51 +0200 Subject: [PATCH 3/6] Rewrite `check_invalid_where_predicate_attrs` --- .../rustc_attr_parsing/src/target_checking.rs | 17 +++++----- tests/ui/attributes/where-doc.rs | 21 ++++++++++++ tests/ui/attributes/where-doc.stderr | 32 +++++++++++++++++++ 3 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 tests/ui/attributes/where-doc.rs create mode 100644 tests/ui/attributes/where-doc.stderr diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs index 65e716921f5cb..8c41d7732c7d5 100644 --- a/compiler/rustc_attr_parsing/src/target_checking.rs +++ b/compiler/rustc_attr_parsing/src/target_checking.rs @@ -292,15 +292,16 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { // in where clauses. After that, this function would become useless. let spans = attrs .into_iter() - // FIXME: We shouldn't need to special-case `doc`! - .filter(|attr| { - matches!( - attr, - Attribute::Parsed(AttributeKind::DocComment { .. } | AttributeKind::Doc(_)) - | Attribute::Unparsed(_) - ) + .filter_map(|attr| { + match attr { + Attribute::Parsed(AttributeKind::DocComment { span, .. }) => Some(*span), + // FIXME: We shouldn't need to special-case `doc`! + Attribute::Parsed(AttributeKind::Doc(attr)) => Some(attr.first_span), + // Checked during attribute parsing target checking + Attribute::Parsed(_) => None, + Attribute::Unparsed(attr) => Some(attr.span), + } }) - .map(|attr| attr.span()) .collect::>(); if !spans.is_empty() { self.dcx() diff --git a/tests/ui/attributes/where-doc.rs b/tests/ui/attributes/where-doc.rs new file mode 100644 index 0000000000000..12fcc71484cb4 --- /dev/null +++ b/tests/ui/attributes/where-doc.rs @@ -0,0 +1,21 @@ +#![feature(where_clause_attrs)] +#![allow(invalid_doc_attributes)] + +fn test() +where +#[doc(alias = ":(")] +//~^ ERROR most attributes are not supported in `where` clauses +//~| ERROR `#[doc(alias = "...")]` isn't allowed on where predicate +():, + +#[doc(hidden)] +//~^ ERROR most attributes are not supported in `where` clauses +():, + +#[doc = ""] +//~^ ERROR most attributes are not supported in `where` clauses +():, + +{ } + +fn main() {} diff --git a/tests/ui/attributes/where-doc.stderr b/tests/ui/attributes/where-doc.stderr new file mode 100644 index 0000000000000..c8efaaeedfaac --- /dev/null +++ b/tests/ui/attributes/where-doc.stderr @@ -0,0 +1,32 @@ +error: most attributes are not supported in `where` clauses + --> $DIR/where-doc.rs:6:1 + | +LL | #[doc(alias = ":(")] + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: only `#[cfg]` and `#[cfg_attr]` are supported + +error: most attributes are not supported in `where` clauses + --> $DIR/where-doc.rs:11:1 + | +LL | #[doc(hidden)] + | ^^^^^^^^^^^^^^ + | + = help: only `#[cfg]` and `#[cfg_attr]` are supported + +error: most attributes are not supported in `where` clauses + --> $DIR/where-doc.rs:15:1 + | +LL | #[doc = ""] + | ^^^^^^^^^^^ + | + = help: only `#[cfg]` and `#[cfg_attr]` are supported + +error: `#[doc(alias = "...")]` isn't allowed on where predicate + --> $DIR/where-doc.rs:6:15 + | +LL | #[doc(alias = ":(")] + | ^^^^ + +error: aborting due to 4 previous errors + From 75fa098069941f0aa4223ead622528cb2ce4982c Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Mon, 27 Apr 2026 09:52:43 +0200 Subject: [PATCH 4/6] Add test for existing bug --- tests/ui/attributes/where-doc.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/ui/attributes/where-doc.rs b/tests/ui/attributes/where-doc.rs index 12fcc71484cb4..761d83966f659 100644 --- a/tests/ui/attributes/where-doc.rs +++ b/tests/ui/attributes/where-doc.rs @@ -16,6 +16,19 @@ where //~^ ERROR most attributes are not supported in `where` clauses ():, +// == That the doc attributes below don't trigger the error is a bug +#[doc()] +():, + +#[doc(5)] +():, + +#[doc] +():, + +#[doc = 5] +():, + { } fn main() {} From 23abf90ff8a0ad5aeae6ea5f344338ab9af97485 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 27 Apr 2026 10:05:57 +0200 Subject: [PATCH 5/6] add test --- .../is-global-norm-binius_field-regression.rs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/ui/traits/next-solver/normalization-shadowing/is-global-norm-binius_field-regression.rs diff --git a/tests/ui/traits/next-solver/normalization-shadowing/is-global-norm-binius_field-regression.rs b/tests/ui/traits/next-solver/normalization-shadowing/is-global-norm-binius_field-regression.rs new file mode 100644 index 0000000000000..bcf3867b65f7c --- /dev/null +++ b/tests/ui/traits/next-solver/normalization-shadowing/is-global-norm-binius_field-regression.rs @@ -0,0 +1,51 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@ check-pass + +// One of the minimizations from trait-system-refactor-initiative#257 which ended up +// getting fixed by #153614. It seems somewhat likely that this is somewhat +// accidental by changing the exact shape of the cycle, causing us to avoid the +// underlying issue of trait-system-refactor-initiative#257. + +pub trait Field: Sized + HasUnderlier> {} +pub trait PackScalar: 'static + UnderlierType { + type Packed; +} +trait PackedField { + type Scalar; +} +pub trait UnderlierType {} +pub trait HasUnderlier { + type Underlier; +} +impl HasUnderlier for U { + type Underlier = U; +} +impl UnderlierType for u8 {} +struct MyField; +impl Field for MyField {} +impl HasUnderlier for MyField { + type Underlier = u8; +} +impl PackScalar for u8 +where + F: Field, +{ + type Packed = PackedPrimitiveType; +} +pub struct PackedPrimitiveType(Scalar); +impl PackedField for PackedPrimitiveType +where + Scalar: Field, +{ + type Scalar = Scalar; +} + +pub trait PackedTransformationFactory {} +trait TaggedPackedTransformationFactory: PackedField {} +impl PackedTransformationFactory for PackedPrimitiveType where + Self: TaggedPackedTransformationFactory +{ +} +fn main() {} From 8c029d5f456775294204a8c28b24d6ba19865d79 Mon Sep 17 00:00:00 2001 From: cypherair <262752927+cypherair@users.noreply.github.com> Date: Sat, 25 Apr 2026 15:46:42 -0700 Subject: [PATCH 6/6] arm64e: set ptrauth ABI subtype on rmeta Mach-O objects Rust packs rlib metadata into a lib.rmeta archive member encoded as a Mach-O object. For Apple arm64e, extend the existing metadata-object subtype special case from bare CPU_SUBTYPE_ARM64E to CPU_SUBTYPE_ARM64E | CPU_SUBTYPE_PTRAUTH_ABI. --- compiler/rustc_codegen_ssa/src/back/metadata.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index cb66dabf507a5..f3e28484bf11d 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -216,7 +216,9 @@ pub(crate) fn create_object_file(sess: &Session) -> Option