From 079ff5b38b919e0c9d494c7f0b493ce04bd47d5a Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 22 May 2026 21:00:16 +0300 Subject: [PATCH 1/4] internal: pin_data: support tuple struct projections Add plumbing for tuple struct support to `#[pin_data]` by generating tuple struct projections and tuple-aware pin-data metadata. Keep the projection API native to Rust tuple structs, so projected fields are accessed as `.0`, `.1`, etc. instead of introducing synthetic public field names. Signed-off-by: Mohamad Alsadhan --- CHANGELOG.md | 1 + internal/Cargo.lock | 2 +- internal/src/pin_data.rs | 357 ++++++++++++------ src/__internal.rs | 41 ++ .../tuple_struct_missing_pin_phantom.rs | 8 + .../tuple_struct_missing_pin_phantom.stderr | 13 + .../tuple_struct_pinned_mutex_not_unpin.rs | 17 + ...tuple_struct_pinned_mutex_not_unpin.stderr | 51 +++ tests/ui/expand/tuple_struct.expanded.rs | 105 ++++++ tests/ui/expand/tuple_struct.rs | 7 + 10 files changed, 489 insertions(+), 113 deletions(-) create mode 100644 tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs create mode 100644 tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.stderr create mode 100644 tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.rs create mode 100644 tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.stderr create mode 100644 tests/ui/expand/tuple_struct.expanded.rs create mode 100644 tests/ui/expand/tuple_struct.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9492a3e0..419b27f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Support tuple structs in `#[pin_data]`, including tuple struct projections. - `[pin_]init_scope` functions to run arbitrary code inside of an initializer. - `&'static mut MaybeUninit` now implements `InPlaceWrite`. This enables users to use external allocation mechanisms such as `static_cell`. diff --git a/internal/Cargo.lock b/internal/Cargo.lock index 245de5da..1a62095f 100644 --- a/internal/Cargo.lock +++ b/internal/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "pin-init-internal" -version = "0.0.6" +version = "0.3.0" dependencies = [ "proc-macro2", "quote", diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 2284256a..1834d894 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -7,7 +7,8 @@ use syn::{ parse_quote, parse_quote_spanned, spanned::Spanned, visit_mut::VisitMut, - Field, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, + Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility, + WhereClause, }; use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; @@ -37,9 +38,24 @@ impl Parse for Args { struct FieldInfo<'a> { field: &'a Field, + member: Member, pinned: bool, } +fn member_ident(member: &Member) -> Ident { + match member { + Member::Named(ident) => ident.clone(), + Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), + } +} + +fn member_display_name(member: &Member) -> String { + match member { + Member::Named(ident) => format!("`{ident}`"), + Member::Unnamed(Index { index, .. }) => format!("index `{index}`"), + } +} + pub(crate) fn pin_data( args: Args, input: Item, @@ -78,30 +94,39 @@ pub(crate) fn pin_data( replacer.visit_generics_mut(&mut struct_.generics); replacer.visit_fields_mut(&mut struct_.fields); + let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_)); let fields: Vec> = struct_ .fields .iter_mut() - .map(|field| { + .enumerate() + .map(|(index, field)| { let len = field.attrs.len(); field.attrs.retain(|a| !a.path().is_ident("pin")); let pinned = len != field.attrs.len(); + let member = match &field.ident { + Some(ident) => Member::Named(ident.clone()), + None => Member::Unnamed(Index { + index: index as u32, + span: field.span(), + }), + }; FieldInfo { field: &*field, + member, pinned, } }) .collect(); for field in &fields { - let ident = field.field.ident.as_ref().unwrap(); - if !field.pinned && is_phantom_pinned(&field.field.ty) { dcx.warn( field.field, format!( - "The field `{ident}` of type `PhantomPinned` only has an effect \ + "The field {} of type `PhantomPinned` only has an effect \ if it has the `#[pin]` attribute", + member_display_name(&field.member), ), ); } @@ -109,10 +134,20 @@ pub(crate) fn pin_data( let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args); - let projections = - generate_projections(&struct_.vis, &struct_.ident, &struct_.generics, &fields); - let the_pin_data = - generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields); + let projections = generate_projections( + &struct_.vis, + &struct_.ident, + &struct_.generics, + is_tuple_struct, + &fields, + ); + let the_pin_data = generate_the_pin_data( + &struct_.vis, + &struct_.ident, + &struct_.generics, + is_tuple_struct, + &fields, + ); Ok(quote! { #struct_ @@ -171,7 +206,14 @@ fn generate_unpin_impl( else { unreachable!() }; - let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| f.field); + let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| { + let Field { attrs, vis, ty, .. } = f.field; + let ident = member_ident(&f.member); + quote!( + #(#attrs)* + #vis #ident: #ty + ) + }); quote! { // This struct will be used for the unpin analysis. It is needed, because only structurally // pinned fields are relevant whether the struct should implement `Unpin`. @@ -247,6 +289,7 @@ fn generate_projections( vis: &Visibility, ident: &Ident, generics: &Generics, + is_tuple_struct: bool, fields: &[FieldInfo<'_>], ) -> TokenStream { let (impl_generics, ty_generics, _) = generics.split_for_impl(); @@ -256,68 +299,121 @@ fn generate_projections( let projection = format_ident!("{ident}Projection"); let this = format_ident!("this"); - let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields - .iter() - .map(|field| { - let Field { - vis, - ident, - ty, - attrs, - .. - } = &field.field; - - let mut no_doc_attrs = attrs.clone(); - no_doc_attrs.retain(|a| !a.path().is_ident("doc")); - let ident = ident - .as_ref() - .expect("only structs with named fields are supported"); - if field.pinned { - ( - quote!( - #(#attrs)* - #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, - ), - quote!( - #(#no_doc_attrs)* - // SAFETY: this field is structurally pinned. - #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, - ), - ) - } else { - ( - quote!( - #(#attrs)* - #vis #ident: &'__pin mut #ty, - ), - quote!( - #(#no_doc_attrs)* - #ident: &mut #this.#ident, - ), - ) - } - }) - .collect(); let structurally_pinned_fields_docs = fields .iter() .filter(|f| f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - {}", member_display_name(&f.member))); let not_structurally_pinned_fields_docs = fields .iter() .filter(|f| !f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - {}", member_display_name(&f.member))); let docs = format!(" Pin-projections of [`{ident}`]"); - quote! { - #[doc = #docs] - #[allow(dead_code)] - #[doc(hidden)] - #vis struct #projection #generics_with_pin_lt - #whr - { - #(#fields_decl)* - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + + let (projection_def, projection_init) = if !is_tuple_struct { + let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields + .iter() + .map(|field| { + let Field { vis, ty, attrs, .. } = &field.field; + let ident = member_ident(&field.member); + let member = &field.member; + + let mut no_doc_attrs = attrs.clone(); + no_doc_attrs.retain(|a| !a.path().is_ident("doc")); + if field.pinned { + ( + quote!( + #(#attrs)* + #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, + ), + quote!( + #(#no_doc_attrs)* + // SAFETY: this field is structurally pinned. + #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) }, + ), + ) + } else { + ( + quote!( + #(#attrs)* + #vis #ident: &'__pin mut #ty, + ), + quote!( + #(#no_doc_attrs)* + #ident: &mut #this.#member, + ), + ) + } + }) + .collect(); + ( + quote! { + #[doc = #docs] + #[allow(dead_code)] + #[doc(hidden)] + #vis struct #projection #generics_with_pin_lt + #whr + { + #(#fields_decl)* + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + } + }, + quote! { + #projection { + #(#fields_proj)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + }, + ) + } else { + let mut fields_decl = Vec::new(); + let mut field_bindings = Vec::new(); + let mut field_projections = Vec::new(); + for (index, field) in fields.iter().enumerate() { + let Field { vis, ty, attrs, .. } = &field.field; + let binding = format_ident!("__field_{index}"); + + if field.pinned { + fields_decl.push(quote!( + #(#attrs)* + #vis ::core::pin::Pin<&'__pin mut #ty>, + )); + field_projections.push(quote!( + // SAFETY: this field is structurally pinned. + unsafe { ::core::pin::Pin::new_unchecked(#binding) }, + )); + } else { + fields_decl.push(quote!( + #(#attrs)* + #vis &'__pin mut #ty, + )); + field_projections.push(quote!(#binding,)); + } + field_bindings.push(quote!(ref mut #binding,)); } + ( + quote! { + #[doc = #docs] + #[allow(dead_code)] + #[doc(hidden)] + #vis struct #projection #generics_with_pin_lt ( + #(#fields_decl)* + ::core::marker::PhantomData<&'__pin mut ()>, + ) #whr; + }, + quote! {{ + let #ident(#(#field_bindings)*) = *#this; + #projection( + #(#field_projections)* + ::core::marker::PhantomData, + ) + }}, + ) + }; + + quote! { + #projection_def + impl #impl_generics #ident #ty_generics #whr { @@ -334,10 +430,7 @@ fn generate_projections( ) -> #projection #ty_generics_with_pin_lt { // SAFETY: we only give access to `&mut` for fields not structurally pinned. let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; - #projection { - #(#fields_proj)* - ___pin_phantom_data: ::core::marker::PhantomData, - } + #projection_init } } } @@ -347,64 +440,104 @@ fn generate_the_pin_data( vis: &Visibility, struct_name: &Ident, generics: &Generics, + is_tuple_struct: bool, fields: &[FieldInfo<'_>], ) -> TokenStream { let (impl_generics, ty_generics, whr) = generics.split_for_impl(); - // For every field, we create an initializing projection function according to its projection - // type. If a field is structurally pinned, we create a `Slot` with `Pinned` which must be - // initialized via `PinInit`; if it is not structurally pinned, then we create a `Slot` with - // `Unpinned` which allows initialization via `Init`. - let field_accessors = fields - .iter() - .map(|f| { - let Field { - vis, - ident, - ty, - attrs, - .. - } = f.field; - - let field_name = ident - .as_ref() - .expect("only structs with named fields are supported"); + let (pin_data_struct, pin_data_new, field_accessors) = if !is_tuple_struct { + // For every field, we create an initializing projection function according to its + // projection type. If a field is structurally pinned, we create a `Slot` with `Pinned` + // which must be initialized via `PinInit`; if it is not structurally pinned, then we + // create a `Slot` with `Unpinned` which allows initialization via `Init`. + let field_accessors = fields + .iter() + .map(|f| { + let Field { vis, ty, attrs, .. } = f.field; + let field_name = member_ident(&f.member); + let member = &f.member; + let pin_marker = if f.pinned { + quote!(Pinned) + } else { + quote!(Unpinned) + }; + quote! { + /// # Safety + /// + /// - `slot` is valid and properly aligned. + /// - `(*slot).#field_name` is properly aligned. + /// - `(*slot).#field_name` points to uninitialized and exclusively accessed + /// memory. + #(#attrs)* + #[inline(always)] + #vis unsafe fn #field_name( + self, + slot: *mut #struct_name #ty_generics, + ) -> ::pin_init::__internal::Slot<::pin_init::__internal::#pin_marker, #ty> { + // SAFETY: + // - If `#pin_marker` is `Pinned`, the corresponding field is structurally + // pinned. + // - Other safety requirements follows the safety requirement. + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#member) } + } + } + }) + .collect::(); + ( + quote! { + #vis struct __ThePinData #generics + #whr + { + __phantom: ::pin_init::__internal::PhantomInvariant<#struct_name #ty_generics>, + } + }, + quote! { + __ThePinData { __phantom: ::pin_init::__internal::PhantomInvariant::new() } + }, + field_accessors, + ) + } else { + let mut field_data = Vec::new(); + let mut field_values = Vec::new(); + for f in fields { + let Field { vis, ty, attrs, .. } = f.field; let pin_marker = if f.pinned { quote!(Pinned) } else { quote!(Unpinned) }; - quote! { - /// # Safety - /// - /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + field_data.push(quote! { #(#attrs)* - #[inline(always)] - #vis unsafe fn #field_name( - self, - slot: *mut #struct_name #ty_generics, - ) -> ::pin_init::__internal::Slot<::pin_init::__internal::#pin_marker, #ty> { - // SAFETY: - // - If `#pin_marker` is `Pinned`, the corresponding field is structurally - // pinned. - // - Other safety requirements follows the safety requirement. - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#field_name) } - } - } - }) - .collect::(); + #vis ::pin_init::__internal::FieldData< + ::pin_init::__internal::#pin_marker, + #ty, + >, + }); + field_values.push(quote! { + #(#attrs)* + ::pin_init::__internal::FieldData::new(), + }); + } + ( + quote! { + #vis struct __ThePinData #generics ( + #(#field_data)* + ) #whr; + }, + quote! { + __ThePinData( + #(#field_values)* + ) + }, + TokenStream::new(), + ) + }; + quote! { // We declare this struct which will host all of the projection function for our type. It // will be invariant over all generic parameters which are inherited from the struct. #[doc(hidden)] - #vis struct __ThePinData #generics - #whr - { - __phantom: ::pin_init::__internal::PhantomInvariant<#struct_name #ty_generics>, - } + #pin_data_struct impl #impl_generics ::core::clone::Clone for __ThePinData #ty_generics #whr @@ -442,7 +575,7 @@ fn generate_the_pin_data( type PinData = __ThePinData #ty_generics; unsafe fn __pin_data() -> Self::PinData { - __ThePinData { __phantom: ::pin_init::__internal::PhantomInvariant::new() } + #pin_data_new } } } diff --git a/src/__internal.rs b/src/__internal.rs index 540add6c..f558f806 100644 --- a/src/__internal.rs +++ b/src/__internal.rs @@ -262,6 +262,47 @@ pub struct Slot { _phantom: PhantomData

, } +pub struct FieldData { + _phantom_pin: PhantomData

, + _phantom_ty: PhantomInvariant, +} + +impl FieldData { + #[inline(always)] + pub const fn new() -> Self { + Self { + _phantom_pin: PhantomData, + _phantom_ty: PhantomInvariant::new(), + } + } + + /// # Safety + /// + /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed + /// memory. + /// - If `P` is [`Pinned`], then `ptr` is structurally pinned. + #[inline(always)] + pub unsafe fn slot(self, ptr: *mut T) -> Slot { + // SAFETY: The caller upholds `Slot::new`'s requirements. + unsafe { Slot::new(ptr) } + } +} + +impl Clone for FieldData { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for FieldData {} + +impl Default for FieldData { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + impl Slot { /// # Safety /// diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs new file mode 100644 index 00000000..bdba06c3 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs @@ -0,0 +1,8 @@ +#![deny(warnings)] + +use pin_init::*; + +#[pin_data] +struct Tuple(T, core::marker::PhantomPinned); + +fn main() {} diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.stderr b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.stderr new file mode 100644 index 00000000..7dfbfdab --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.stderr @@ -0,0 +1,13 @@ +error: use of deprecated function `_::warn`: + The field index `1` of type `PhantomPinned` only has an effect if it has the `#[pin]` attribute + --> tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs:6:20 + | +6 | struct Tuple(T, core::marker::PhantomPinned); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/compile-fail/pin_data/tuple_struct_missing_pin_phantom.rs:1:9 + | +1 | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(deprecated)]` implied by `#[deny(warnings)]` diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.rs b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.rs new file mode 100644 index 00000000..4edf2250 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.rs @@ -0,0 +1,17 @@ +#![cfg_attr(feature = "alloc", feature(allocator_api))] + +use pin_init::*; + +#[allow(unused_attributes)] +#[path = "../../../../examples/mutex.rs"] +mod mutex; +use mutex::CMutex; + +#[pin_data] +struct Tuple(#[pin] CMutex, usize); + +fn assert_unpin() {} + +fn main() { + assert_unpin::>(); +} diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.stderr b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.stderr new file mode 100644 index 00000000..2b240c75 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.stderr @@ -0,0 +1,51 @@ +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.rs:16:20 + | +16 | assert_unpin::>(); + | ^^^^^^^^^^^^ within `mutex::linked_list::_::__Unpin<'_>`, the trait `Unpin` is not implemented for `PhantomPinned` + | + = note: consider using the `pin!` macro + consider using `Box::pin` if you need to access the pinned value outside of the current scope +note: required because it appears within the type `mutex::linked_list::_::__Unpin<'_>` + --> tests/ui/compile-fail/pin_data/../../../../examples/./linked_list.rs + | + | #[pin_data(PinnedDrop)] + | ^^^^^^^^^^^^^^^^^^^^^^^ +note: required for `mutex::linked_list::ListHead` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/../../../../examples/./linked_list.rs + | + | #[pin_data(PinnedDrop)] + | ^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced here +... + | pub struct ListHead { + | ^^^^^^^^ +note: required because it appears within the type `mutex::_::__Unpin<'_, usize>` + --> tests/ui/compile-fail/pin_data/../../../../examples/mutex.rs + | + | #[pin_data] + | ^^^^^^^^^^^ +note: required for `CMutex` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/../../../../examples/mutex.rs + | + | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here + | pub struct CMutex { + | ^^^^^^^^^ +note: required because it appears within the type `_::__Unpin<'_, usize>` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.rs:10:1 + | +10 | #[pin_data] + | ^^^^^^^^^^^ +note: required for `Tuple` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.rs:10:1 + | +10 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here +11 | struct Tuple(#[pin] CMutex, usize); + | ^^^^^^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_mutex_not_unpin.rs:13:20 + | +13 | fn assert_unpin() {} + | ^^^^^ required by this bound in `assert_unpin` + = note: this error originates in the attribute macro `pin_data` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs new file mode 100644 index 00000000..08068064 --- /dev/null +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -0,0 +1,105 @@ +use core::marker::PhantomPinned; +use pin_init::*; +struct Foo<'a, T: Copy, const N: usize>(&'a mut [T; N], PhantomPinned, usize); +/// Pin-projections of [`Foo`] +#[allow(dead_code)] +#[doc(hidden)] +struct FooProjection<'__pin, 'a, T: Copy, const N: usize>( + &'__pin mut &'a mut [T; N], + ::core::pin::Pin<&'__pin mut PhantomPinned>, + &'__pin mut usize, + ::core::marker::PhantomData<&'__pin mut ()>, +); +impl<'a, T: Copy, const N: usize> Foo<'a, T, N> { + /// Pin-projects all fields of `Self`. + /// + /// These fields are structurally pinned: + /// - index `1` + /// + /// These fields are **not** structurally pinned: + /// - index `0` + /// - index `2` + #[inline] + fn project<'__pin>( + self: ::core::pin::Pin<&'__pin mut Self>, + ) -> FooProjection<'__pin, 'a, T, N> { + let this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + { + let Foo(ref mut __field_0, ref mut __field_1, ref mut __field_2) = *this; + FooProjection( + __field_0, + unsafe { ::core::pin::Pin::new_unchecked(__field_1) }, + __field_2, + ::core::marker::PhantomData, + ) + } + } +} +const _: () = { + #[doc(hidden)] + struct __ThePinData<'a, T: Copy, const N: usize>( + ::pin_init::__internal::FieldData< + ::pin_init::__internal::Unpinned, + &'a mut [T; N], + >, + ::pin_init::__internal::FieldData<::pin_init::__internal::Pinned, PhantomPinned>, + ::pin_init::__internal::FieldData<::pin_init::__internal::Unpinned, usize>, + ); + impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __ThePinData<'a, T, N> { + fn clone(&self) -> Self { + *self + } + } + impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __ThePinData<'a, T, N> {} + #[allow(dead_code)] + #[expect(clippy::missing_safety_doc)] + impl<'a, T: Copy, const N: usize> __ThePinData<'a, T, N> { + /// Type inference helper function. + #[inline(always)] + fn __make_closure<__F, __E>(self, f: __F) -> __F + where + __F: FnOnce( + *mut Foo<'a, T, N>, + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, + { + f + } + } + unsafe impl<'a, T: Copy, const N: usize> ::pin_init::__internal::HasPinData + for Foo<'a, T, N> { + type PinData = __ThePinData<'a, T, N>; + unsafe fn __pin_data() -> Self::PinData { + __ThePinData( + ::pin_init::__internal::FieldData::new(), + ::pin_init::__internal::FieldData::new(), + ::pin_init::__internal::FieldData::new(), + ) + } + } + #[allow(dead_code)] + struct __Unpin<'__pin, 'a, T: Copy, const N: usize> { + __phantom_pin: ::pin_init::__internal::PhantomInvariantLifetime<'__pin>, + __phantom: ::pin_init::__internal::PhantomInvariant>, + _1: PhantomPinned, + } + #[doc(hidden)] + impl<'__pin, 'a, T: Copy, const N: usize> ::core::marker::Unpin for Foo<'a, T, N> + where + __Unpin<'__pin, 'a, T, N>: ::core::marker::Unpin, + {} + trait MustNotImplDrop {} + #[expect(drop_bounds)] + impl MustNotImplDrop for T {} + impl<'a, T: Copy, const N: usize> MustNotImplDrop for Foo<'a, T, N> {} + #[expect(non_camel_case_types)] + trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} + impl< + T: ::pin_init::PinnedDrop + ?::core::marker::Sized, + > UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} + impl< + 'a, + T: Copy, + const N: usize, + > UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for Foo<'a, T, N> {} +}; +fn main() {} diff --git a/tests/ui/expand/tuple_struct.rs b/tests/ui/expand/tuple_struct.rs new file mode 100644 index 00000000..d81daa1e --- /dev/null +++ b/tests/ui/expand/tuple_struct.rs @@ -0,0 +1,7 @@ +use core::marker::PhantomPinned; +use pin_init::*; + +#[pin_data] +struct Foo<'a, T: Copy, const N: usize>(&'a mut [T; N], #[pin] PhantomPinned, usize); + +fn main() {} From c292508734bed5d340d10688873d58c2eb6979d5 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 22 May 2026 21:03:59 +0300 Subject: [PATCH 2/4] internal: init: add tuple struct field and constructor syntax Expand `init!` and `pin_init!` to initialize tuple structs using both indexed brace syntax and tuple-constructor syntax. Add tuple-specific runtime and UI coverage for the basic initializer forms, duplicate/missing fields, invalid indices, and syntax errors. Signed-off-by: Mohamad Alsadhan --- CHANGELOG.md | 4 +- internal/src/init.rs | 229 +++++++++++++----- tests/tuple_struct.rs | 67 +++++ .../compile-fail/init/no_tuple_paren_arrow.rs | 8 + .../init/no_tuple_paren_arrow.stderr | 5 + .../compile-fail/init/no_tuple_shorthand.rs | 8 + .../init/no_tuple_shorthand.stderr | 5 + .../init/no_tuple_syntax_mixing.rs | 8 + .../init/no_tuple_syntax_mixing.stderr | 5 + .../init/tuple_duplicate_field.rs | 8 + .../init/tuple_duplicate_field.stderr | 8 + .../compile-fail/init/tuple_invalid_field.rs | 8 + .../init/tuple_invalid_field.stderr | 30 +++ .../compile-fail/init/tuple_missing_field.rs | 9 + .../init/tuple_missing_field.stderr | 11 + tests/ui/expand/tuple_struct.expanded.rs | 119 ++++++++- tests/ui/expand/tuple_struct.rs | 12 +- 17 files changed, 480 insertions(+), 64 deletions(-) create mode 100644 tests/tuple_struct.rs create mode 100644 tests/ui/compile-fail/init/no_tuple_paren_arrow.rs create mode 100644 tests/ui/compile-fail/init/no_tuple_paren_arrow.stderr create mode 100644 tests/ui/compile-fail/init/no_tuple_shorthand.rs create mode 100644 tests/ui/compile-fail/init/no_tuple_shorthand.stderr create mode 100644 tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs create mode 100644 tests/ui/compile-fail/init/no_tuple_syntax_mixing.stderr create mode 100644 tests/ui/compile-fail/init/tuple_duplicate_field.rs create mode 100644 tests/ui/compile-fail/init/tuple_duplicate_field.stderr create mode 100644 tests/ui/compile-fail/init/tuple_invalid_field.rs create mode 100644 tests/ui/compile-fail/init/tuple_invalid_field.stderr create mode 100644 tests/ui/compile-fail/init/tuple_missing_field.rs create mode 100644 tests/ui/compile-fail/init/tuple_missing_field.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 419b27f8..98e7772b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - -- Support tuple structs in `#[pin_data]`, including tuple struct projections. +- Support tuple structs in `#[pin_data]`, including tuple struct projections, and tuple struct + initialization in `init!` and `pin_init!`. - `[pin_]init_scope` functions to run arbitrary code inside of an initializer. - `&'static mut MaybeUninit` now implements `InPlaceWrite`. This enables users to use external allocation mechanisms such as `static_cell`. diff --git a/internal/src/init.rs b/internal/src/init.rs index 699b1055..b65c52fc 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -3,12 +3,13 @@ use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{ - braced, + braced, parenthesized, parse::{End, Parse}, parse_quote, punctuated::Punctuated, spanned::Spanned, - token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, + token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Index, LitInt, Member, Path, Token, + Type, }; use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; @@ -17,7 +18,7 @@ pub(crate) struct Initializer { attrs: Vec, this: Option, path: Path, - brace_token: token::Brace, + close_span: Span, fields: Punctuated, rest: Option<(Token![..], Expr)>, error: Option<(Token![?], Type)>, @@ -34,13 +35,18 @@ struct InitializerField { kind: InitializerKind, } +struct TupleInitializerField { + attrs: Vec, + value: Expr, +} + enum InitializerKind { Value { - ident: Ident, + member: Member, value: Option<(Token![:], Expr)>, }, Init { - ident: Ident, + member: Member, _left_arrow_token: Token![<-], value: Expr, }, @@ -52,14 +58,28 @@ enum InitializerKind { } impl InitializerKind { - fn ident(&self) -> Option<&Ident> { + fn member(&self) -> Option<&Member> { match self { - Self::Value { ident, .. } | Self::Init { ident, .. } => Some(ident), + Self::Value { member, .. } | Self::Init { member, .. } => Some(member), Self::Code { .. } => None, } } } +fn member_ident(member: &Member) -> Ident { + match member { + Member::Named(ident) => ident.clone(), + Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), + } +} + +fn member_binding(member: &Member) -> Option { + match member { + Member::Named(ident) => Some(ident.clone()), + Member::Unnamed(_) => None, + } +} + enum InitializerAttribute { DefaultError(DefaultErrorAttribute), } @@ -73,7 +93,7 @@ pub(crate) fn expand( attrs, this, path, - brace_token, + close_span, fields, rest, error, @@ -96,7 +116,7 @@ pub(crate) fn expand( } else if let Some(default_error) = default_error { syn::parse_str(default_error).unwrap() } else { - dcx.error(brace_token.span.close(), "expected `? ` after `}`"); + dcx.error(close_span, "expected `? ` after initializer"); parse_quote!(::core::convert::Infallible) } }, @@ -229,9 +249,9 @@ fn init_fields( cfgs }; - let ident = match kind { - InitializerKind::Value { ident, .. } => ident, - InitializerKind::Init { ident, .. } => ident, + let member = match kind { + InitializerKind::Value { member, .. } => member, + InitializerKind::Init { member, .. } => member, InitializerKind::Code { block, .. } => { res.extend(quote! { #(#attrs)* @@ -241,27 +261,38 @@ fn init_fields( continue; } }; + let ident = member_ident(member); let slot = if pinned { - quote! { - // SAFETY: - // - `slot` is valid and properly aligned. - // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned. - // - `make_field_check` prevents `#ident` from being used twice, therefore - // `(*slot).#ident` is exclusively accessed and has not been initialized. - (unsafe { #data.#ident(#slot) }) + match member { + Member::Named(_) => quote! { + // SAFETY: + // - `slot` is valid and properly aligned. + // - `make_field_check` checks that the field is properly aligned. + // - `make_field_check` prevents the field from being used twice, therefore + // it is exclusively accessed and has not been initialized. + (unsafe { #data.#ident(#slot) }) + }, + Member::Unnamed(_) => quote! { + // SAFETY: + // - `slot` is valid and properly aligned. + // - `make_field_check` checks that the field is properly aligned. + // - `make_field_check` prevents the field from being used twice, therefore + // it is exclusively accessed and has not been initialized. + (unsafe { #data.#member.slot(&raw mut (*#slot).#member) }) + }, } } else { quote! { // For `init!()` macro, everything is unpinned. // SAFETY: - // - `&raw mut (*slot).#ident` is valid. - // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned. - // - `make_field_check` prevents `#ident` from being used twice, therefore - // `(*slot).#ident` is exclusively accessed and has not been initialized. + // - The field pointer is valid. + // - `make_field_check` checks that the field is properly aligned. + // - `make_field_check` prevents the field from being used twice, therefore + // it is exclusively accessed and has not been initialized. (unsafe { ::pin_init::__internal::Slot::<::pin_init::__internal::Unpinned, _>::new( - &raw mut (*#slot).#ident + &raw mut (*#slot).#member ) }) } @@ -271,7 +302,7 @@ fn init_fields( let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); let init = match kind { - InitializerKind::Value { ident, value } => { + InitializerKind::Value { value, .. } => { let value = value .as_ref() .map(|(_, value)| quote!(#value)) @@ -291,13 +322,18 @@ fn init_fields( } InitializerKind::Code { .. } => unreachable!(), }; + let binding = member_binding(member).map(|ident| { + quote! { + #(#cfgs)* + #[allow(unused_variables)] + let #ident = #guard.let_binding(); + } + }); res.extend(quote! { #init - #(#cfgs)* - #[allow(unused_variables)] - let #ident = #guard.let_binding(); + #binding }); guards.push(guard); @@ -322,9 +358,9 @@ fn make_field_check( ) -> TokenStream { let field_attrs: Vec<_> = fields .iter() - .filter_map(|f| f.kind.ident().map(|_| &f.attrs)) + .filter_map(|f| f.kind.member().map(|_| &f.attrs)) .collect(); - let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.ident()).collect(); + let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.member()).collect(); let zeroing_trailer = match init_kind { InitKind::Normal => None, InitKind::Zeroing => Some(quote! { @@ -360,36 +396,73 @@ fn make_field_check( } } -impl Parse for Initializer { - fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { - let attrs = input.call(Attribute::parse_outer)?; - let this = input.peek(Token![&]).then(|| input.parse()).transpose()?; - let path = input.parse()?; - let content; - let brace_token = braced!(content in input); - let mut fields = Punctuated::new(); - loop { +type InitFields = Punctuated; +type InitRest = Option<(Token![..], Expr)>; + +fn parse_brace_initializer( + input: syn::parse::ParseStream<'_>, +) -> syn::Result<(Span, InitFields, InitRest)> { + let content; + let brace_token = braced!(content in input); + let mut fields = Punctuated::new(); + loop { + let lh = content.lookahead1(); + if lh.peek(End) || lh.peek(Token![..]) { + break; + } else if lh.peek(Ident) || lh.peek(LitInt) || lh.peek(Token![_]) || lh.peek(Token![#]) { + fields.push_value(content.parse()?); let lh = content.lookahead1(); - if lh.peek(End) || lh.peek(Token![..]) { + if lh.peek(End) { break; - } else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Token![#]) { - fields.push_value(content.parse()?); - let lh = content.lookahead1(); - if lh.peek(End) { - break; - } else if lh.peek(Token![,]) { - fields.push_punct(content.parse()?); - } else { - return Err(lh.error()); - } + } else if lh.peek(Token![,]) { + fields.push_punct(content.parse()?); } else { return Err(lh.error()); } + } else { + return Err(lh.error()); } - let rest = content - .peek(Token![..]) - .then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?))) - .transpose()?; + } + let rest = content + .peek(Token![..]) + .then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?))) + .transpose()?; + Ok((brace_token.span.close(), fields, rest)) +} + +fn parse_paren_initializer(input: syn::parse::ParseStream<'_>) -> syn::Result<(Span, InitFields)> { + let content; + let paren_token = parenthesized!(content in input); + let tuple_fields = content.parse_terminated(TupleInitializerField::parse, Token![,])?; + let mut fields = Punctuated::new(); + for (index, tuple_field) in tuple_fields.into_iter().enumerate() { + fields.push(InitializerField { + attrs: tuple_field.attrs, + kind: InitializerKind::Value { + member: Member::Unnamed(Index { + index: index as u32, + span: tuple_field.value.span(), + }), + value: Some((Token![:](tuple_field.value.span()), tuple_field.value)), + }, + }); + } + Ok((paren_token.span.close(), fields)) +} + +impl Parse for Initializer { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; + let this = input.peek(Token![&]).then(|| input.parse()).transpose()?; + let path = input.parse()?; + let (close_span, fields, rest) = if input.peek(token::Brace) { + parse_brace_initializer(input)? + } else if input.peek(token::Paren) { + let (close_span, fields) = parse_paren_initializer(input)?; + (close_span, fields, None) + } else { + return Err(input.error("expected curly braces or parentheses")); + }; let error = input .peek(Token![?]) .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) @@ -409,7 +482,7 @@ impl Parse for Initializer { attrs, this, path, - brace_token, + close_span, fields, rest, error, @@ -452,22 +525,43 @@ impl Parse for InitializerKind { _colon_token: input.parse()?, block: input.parse()?, }) - } else if lh.peek(Ident) { - let ident = input.parse()?; + } else if lh.peek(Ident) || lh.peek(LitInt) { + let member = if lh.peek(Ident) { + Member::Named(input.parse()?) + } else { + let lit: LitInt = input.parse()?; + let index: u32 = lit.base10_parse().map_err(|_| { + syn::Error::new( + lit.span(), + "tuple field index must be a non-negative integer", + ) + })?; + Member::Unnamed(Index { + index, + span: lit.span(), + }) + }; let lh = input.lookahead1(); if lh.peek(Token![<-]) { Ok(Self::Init { - ident, + member, _left_arrow_token: input.parse()?, value: input.parse()?, }) } else if lh.peek(Token![:]) { Ok(Self::Value { - ident, + member, value: Some((input.parse()?, input.parse()?)), }) } else if lh.peek(Token![,]) || lh.peek(End) { - Ok(Self::Value { ident, value: None }) + if matches!(member, Member::Unnamed(_)) { + Err(lh.error()) + } else { + Ok(Self::Value { + member, + value: None, + }) + } } else { Err(lh.error()) } @@ -476,3 +570,18 @@ impl Parse for InitializerKind { } } } + +impl Parse for TupleInitializerField { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; + if input.peek(Token![<-]) { + return Err(input.error( + "`<-` is not supported in tuple constructor syntax; use braces with indices, e.g. `Type { 0 <- init, 1: value }`", + )); + } + Ok(Self { + attrs, + value: input.parse()?, + }) + } +} diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs new file mode 100644 index 00000000..48ec89ee --- /dev/null +++ b/tests/tuple_struct.rs @@ -0,0 +1,67 @@ +#![cfg_attr(feature = "alloc", feature(allocator_api))] + +use core::pin::Pin; +use pin_init::*; + +#[allow(unused_attributes)] +#[path = "../examples/mutex.rs"] +mod mutex; +use mutex::*; + +#[pin_data] +struct TupleStruct(#[pin] CMutex, i32); + +fn assert_pinned_mutex(_: &Pin<&mut CMutex>) {} + +#[test] +fn tuple_struct_brace_init_and_projection() { + stack_pin_init!(let tuple = pin_init!(TupleStruct:: { 0 <- CMutex::new(7), 1: 13 })); + + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected.0); + assert_eq!(*projected.0.as_ref().get_ref().lock(), 7); + assert_eq!(*projected.1, 13); +} + +#[pin_data] +struct Triple(i32, i32, i32); + +#[pin_data] +struct ValueTuple(T, i32); + +#[pin_data] +struct DualPinned(#[pin] CMutex, #[pin] CMutex, usize); + +#[test] +fn tuple_struct_constructor_form() { + stack_pin_init!(let triple = pin_init!(Triple(11, 29, 31))); + assert_eq!(triple.as_ref().get_ref().0, 11); + assert_eq!(triple.as_ref().get_ref().1, 29); + assert_eq!(triple.as_ref().get_ref().2, 31); +} + +#[test] +fn tuple_struct_constructor_infers_generics() { + stack_pin_init!(let tuple = pin_init!(ValueTuple(9u32, 6))); + assert_eq!(tuple.as_ref().get_ref().0, 9u32); + assert_eq!(tuple.as_ref().get_ref().1, 6); +} + +#[test] +fn tuple_struct_multi_pinned_fields_projection() { + stack_pin_init!( + let tuple = pin_init!(DualPinned:: { 0 <- CMutex::new(1), 1 <- CMutex::new(2), 2: 3 }) + ); + + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected.0); + assert_pinned_mutex(&projected.1); + + *projected.0.as_ref().get_ref().lock() = 10; + *projected.1.as_ref().get_ref().lock() = 20; + *projected.2 = 30; + + assert_eq!(*tuple.as_ref().get_ref().0.lock(), 10); + assert_eq!(*tuple.as_ref().get_ref().1.lock(), 20); + assert_eq!(tuple.as_ref().get_ref().2, 30); +} diff --git a/tests/ui/compile-fail/init/no_tuple_paren_arrow.rs b/tests/ui/compile-fail/init/no_tuple_paren_arrow.rs new file mode 100644 index 00000000..2df60080 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_paren_arrow.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple(<- 1, 2)); +} diff --git a/tests/ui/compile-fail/init/no_tuple_paren_arrow.stderr b/tests/ui/compile-fail/init/no_tuple_paren_arrow.stderr new file mode 100644 index 00000000..55ddc3dc --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_paren_arrow.stderr @@ -0,0 +1,5 @@ +error: `<-` is not supported in tuple constructor syntax; use braces with indices, e.g. `Type { 0 <- init, 1: value }` + --> tests/ui/compile-fail/init/no_tuple_paren_arrow.rs:7:29 + | +7 | let _ = pin_init!(Tuple(<- 1, 2)); + | ^ diff --git a/tests/ui/compile-fail/init/no_tuple_shorthand.rs b/tests/ui/compile-fail/init/no_tuple_shorthand.rs new file mode 100644 index 00000000..4b0297f4 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_shorthand.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple { 0, 1: 24 }); +} diff --git a/tests/ui/compile-fail/init/no_tuple_shorthand.stderr b/tests/ui/compile-fail/init/no_tuple_shorthand.stderr new file mode 100644 index 00000000..f78d85fa --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_shorthand.stderr @@ -0,0 +1,5 @@ +error: expected `<-` or `:` + --> tests/ui/compile-fail/init/no_tuple_shorthand.rs:7:32 + | +7 | let _ = pin_init!(Tuple { 0, 1: 24 }); + | ^ diff --git a/tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs new file mode 100644 index 00000000..9a7e3c84 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple (0, 1: 24)); +} diff --git a/tests/ui/compile-fail/init/no_tuple_syntax_mixing.stderr b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.stderr new file mode 100644 index 00000000..81c8fe02 --- /dev/null +++ b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.stderr @@ -0,0 +1,5 @@ +error: expected `,` + --> tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs:7:34 + | +7 | let _ = pin_init!(Tuple (0, 1: 24)); + | ^ diff --git a/tests/ui/compile-fail/init/tuple_duplicate_field.rs b/tests/ui/compile-fail/init/tuple_duplicate_field.rs new file mode 100644 index 00000000..971b3f97 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_duplicate_field.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple { 0: 1, 0: 2, 1: 3 }); +} diff --git a/tests/ui/compile-fail/init/tuple_duplicate_field.stderr b/tests/ui/compile-fail/init/tuple_duplicate_field.stderr new file mode 100644 index 00000000..dd57ac30 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_duplicate_field.stderr @@ -0,0 +1,8 @@ +error[E0062]: field `0` specified more than once + --> tests/ui/compile-fail/init/tuple_duplicate_field.rs:7:37 + | +7 | let _ = pin_init!(Tuple { 0: 1, 0: 2, 1: 3 }); + | ------------------------^------------ + | | | + | | used more than once + | first use of `0` diff --git a/tests/ui/compile-fail/init/tuple_invalid_field.rs b/tests/ui/compile-fail/init/tuple_invalid_field.rs new file mode 100644 index 00000000..19663284 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_invalid_field.rs @@ -0,0 +1,8 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); +} diff --git a/tests/ui/compile-fail/init/tuple_invalid_field.stderr b/tests/ui/compile-fail/init/tuple_invalid_field.stderr new file mode 100644 index 00000000..f705adaf --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_invalid_field.stderr @@ -0,0 +1,30 @@ +error[E0609]: no field `2` on type `__ThePinData` + --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:43 + | +7 | let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); + | ^ unknown field + | + = note: available fields are: `0`, `1` + +error[E0609]: no field `2` on type `Tuple` + --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:43 + | +7 | let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); + | ^ unknown field + | + = note: available fields are: `0`, `1` + +error[E0560]: struct `Tuple` has no field named `2` + --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:43 + | +4 | struct Tuple(#[pin] i32, i32); + | ----- `Tuple` defined here +... +7 | let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); + | ^ field does not exist + | +help: `Tuple` is a tuple struct, use the appropriate syntax + | +7 - let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); +7 + let _ = Tuple(/* i32 */, /* i32 */); + | diff --git a/tests/ui/compile-fail/init/tuple_missing_field.rs b/tests/ui/compile-fail/init/tuple_missing_field.rs new file mode 100644 index 00000000..401ded40 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_missing_field.rs @@ -0,0 +1,9 @@ +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] i32, i32); + +fn main() { + let _ = pin_init!(Tuple { 0: 1 }); + let _ = init!(Tuple { 0: 1 }); +} diff --git a/tests/ui/compile-fail/init/tuple_missing_field.stderr b/tests/ui/compile-fail/init/tuple_missing_field.stderr new file mode 100644 index 00000000..4e5ad4c8 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_missing_field.stderr @@ -0,0 +1,11 @@ +error[E0063]: missing field `1` in initializer of `Tuple` + --> tests/ui/compile-fail/init/tuple_missing_field.rs:7:23 + | +7 | let _ = pin_init!(Tuple { 0: 1 }); + | ^^^^^ missing `1` + +error[E0063]: missing field `1` in initializer of `Tuple` + --> tests/ui/compile-fail/init/tuple_missing_field.rs:8:19 + | +8 | let _ = init!(Tuple { 0: 1 }); + | ^^^^^ missing `1` diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs index 08068064..4c8ba6cb 100644 --- a/tests/ui/expand/tuple_struct.expanded.rs +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -102,4 +102,121 @@ const _: () = { const N: usize, > UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for Foo<'a, T, N> {} }; -fn main() {} +fn main() { + let mut first = [1u8, 2, 3]; + let _ = { + let __data = unsafe { + use ::pin_init::__internal::HasInitData; + Foo::__init_data() + }; + let init = __data + .__make_closure::< + _, + ::core::convert::Infallible, + >(move |slot| { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut first); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .init(10)?; + ::core::mem::forget(___0_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___2_guard); + #[allow(unreachable_code, clippy::diverging_sub_expression)] + let _ = || unsafe { + let _ = &(*slot).0; + let _ = &(*slot).1; + let _ = &(*slot).2; + ::core::ptr::write( + slot, + Foo { + 0: ::core::panicking::panic("explicit panic"), + 1: ::core::panicking::panic("explicit panic"), + 2: ::core::panicking::panic("explicit panic"), + }, + ) + }; + Ok(unsafe { ::pin_init::__internal::InitOk::new() }) + }); + let init = move | + slot, + | -> ::core::result::Result<(), ::core::convert::Infallible> { + init(slot).map(|__InitOk| ()) + }; + unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } + }; + let mut second = [4u8, 5, 6]; + let _ = { + let __data = unsafe { + use ::pin_init::__internal::HasInitData; + Foo::__init_data() + }; + let init = __data + .__make_closure::< + _, + ::core::convert::Infallible, + >(move |slot| { + let mut ___0_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).0) + }) + .write(&mut second); + let mut ___1_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).1) + }) + .write(PhantomPinned); + let mut ___2_guard = (unsafe { + ::pin_init::__internal::Slot::< + ::pin_init::__internal::Unpinned, + _, + >::new(&raw mut (*slot).2) + }) + .write(20); + ::core::mem::forget(___0_guard); + ::core::mem::forget(___1_guard); + ::core::mem::forget(___2_guard); + #[allow(unreachable_code, clippy::diverging_sub_expression)] + let _ = || unsafe { + let _ = &(*slot).0; + let _ = &(*slot).1; + let _ = &(*slot).2; + ::core::ptr::write( + slot, + Foo { + 0: ::core::panicking::panic("explicit panic"), + 1: ::core::panicking::panic("explicit panic"), + 2: ::core::panicking::panic("explicit panic"), + }, + ) + }; + Ok(unsafe { ::pin_init::__internal::InitOk::new() }) + }); + let init = move | + slot, + | -> ::core::result::Result<(), ::core::convert::Infallible> { + init(slot).map(|__InitOk| ()) + }; + unsafe { ::pin_init::init_from_closure::<_, ::core::convert::Infallible>(init) } + }; +} diff --git a/tests/ui/expand/tuple_struct.rs b/tests/ui/expand/tuple_struct.rs index d81daa1e..27d8c7f7 100644 --- a/tests/ui/expand/tuple_struct.rs +++ b/tests/ui/expand/tuple_struct.rs @@ -4,4 +4,14 @@ use pin_init::*; #[pin_data] struct Foo<'a, T: Copy, const N: usize>(&'a mut [T; N], #[pin] PhantomPinned, usize); -fn main() {} +fn main() { + let mut first = [1u8, 2, 3]; + let _ = init!(Foo { + 0: &mut first, + 1: PhantomPinned, + 2 <- 10, + }); + + let mut second = [4u8, 5, 6]; + let _ = init!(Foo(&mut second, PhantomPinned, 20)); +} From 28e80bb137f0351830f6d5afd35775d1f9062a84 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 22 May 2026 21:04:29 +0300 Subject: [PATCH 3/4] tests: add tuple struct generic and drop coverage Extend tuple-struct coverage with the semantic cases that are easiest to review independently from the implementation changes. Add runtime coverage for generic and const-generic tuple structs, `Unpin` behaviour with `!Unpin` field types, pinned drop, and fallible partial-init rollback. Signed-off-by: Mohamad Alsadhan --- tests/tuple_struct.rs | 108 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs index 48ec89ee..c2508056 100644 --- a/tests/tuple_struct.rs +++ b/tests/tuple_struct.rs @@ -65,3 +65,111 @@ fn tuple_struct_multi_pinned_fields_projection() { assert_eq!(*tuple.as_ref().get_ref().1.lock(), 20); assert_eq!(tuple.as_ref().get_ref().2, 30); } + +#[pin_data] +struct GenericTuple<'a, T, const N: usize>(#[pin] CMutex<(&'a T, [u8; N])>, usize); + +#[pin_data] +#[allow(dead_code)] +struct UnpinnedMutexTuple(CMutex, usize); + +#[pin_data] +struct TupleConst(#[pin] CMutex<[T; N]>, usize); + +fn assert_unpin() {} + +#[test] +fn tuple_struct_generics_are_supported() { + let value = 77u16; + let payload = (&value, [1, 2, 3, 4]); + stack_pin_init!( + let tuple = pin_init!(GenericTuple { 0 <- CMutex::new(payload), 1: 12 }) + ); + + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected.0); + let locked = projected.0.as_ref().get_ref().lock(); + assert_eq!(*locked.0, 77u16); + assert_eq!(locked.1, [1, 2, 3, 4]); + assert_eq!(*projected.1, 12); +} + +#[test] +fn tuple_struct_unpin_ignores_unpinned_non_unpin_field() { + assert_unpin::>(); +} + +#[test] +fn tuple_struct_const_generics_support_explicit_arguments() { + stack_pin_init!(let tuple = pin_init!(TupleConst:: { 0 <- CMutex::new([1, 2, 3]), 1: 9 })); + + let projected = tuple.as_mut().project(); + assert_pinned_mutex(&projected.0); + assert_eq!(*projected.0.as_ref().get_ref().lock(), [1, 2, 3]); + assert_eq!(*projected.1, 9); +} + +#[pin_data(PinnedDrop)] +struct DropTuple(#[pin] CMutex, usize); + +static PINNED_DROP_TUPLE_DROPS: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +struct DropCounter; + +static FALLIBLE_TUPLE_DROPS: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +impl Drop for DropCounter { + fn drop(&mut self) { + FALLIBLE_TUPLE_DROPS.fetch_add(1, core::sync::atomic::Ordering::SeqCst); + } +} + +fn tuple_failing_init() -> impl PinInit, ()> { + // SAFETY: The closure initializes field 0, explicitly rolls it back before returning `Err`, + // and leaves the slot otherwise untouched. + unsafe { + pin_init_from_closure(|slot: *mut TupleStruct| { + let field0 = core::ptr::addr_of_mut!((*slot).0); + let init0 = CMutex::new(DropCounter); + match init0.__pinned_init(field0) { + Ok(()) => {} + Err(infallible) => match infallible {}, + } + core::ptr::drop_in_place(field0); + Err(()) + }) + } +} + +#[pinned_drop] +impl PinnedDrop for DropTuple { + fn drop(self: Pin<&mut Self>) { + let _ = self; + PINNED_DROP_TUPLE_DROPS.fetch_add(1, core::sync::atomic::Ordering::SeqCst); + } +} + +#[test] +fn tuple_struct_pinned_drop_delegates_from_drop() { + PINNED_DROP_TUPLE_DROPS.store(0, core::sync::atomic::Ordering::SeqCst); + { + stack_pin_init!(let _tuple = pin_init!(DropTuple { 0 <- CMutex::new(5usize), 1: 1 })); + } + assert_eq!( + PINNED_DROP_TUPLE_DROPS.load(core::sync::atomic::Ordering::SeqCst), + 1 + ); +} + +#[test] +fn tuple_struct_fallible_init_drops_initialized_fields() { + FALLIBLE_TUPLE_DROPS.store(0, core::sync::atomic::Ordering::SeqCst); + stack_try_pin_init!(let tuple: TupleStruct = tuple_failing_init()); + assert!(matches!(tuple, Err(()))); + assert_eq!( + FALLIBLE_TUPLE_DROPS.load(core::sync::atomic::Ordering::SeqCst), + 1 + ); +} From 1f2c2798b4a3571fe45c88becd9ec59d81083d47 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 22 May 2026 21:05:02 +0300 Subject: [PATCH 4/4] internal: init: support cfg-stripped tuple fields and arguments Handle tuple structs whose fields or constructor arguments are removed by `#[cfg]`. For tuple projections and tuple-constructor lowering, generate the needed cfg-specific layouts instead of trying to evaluate user cfgs inside the proc macro, and let rustc select the active branch. Add regression tests covering cfg-stripped tuple fields, cfg-stripped tuple constructor arguments, and feature-dependent field layouts. Signed-off-by: Mohamad Alsadhan --- internal/src/init.rs | 82 ++++++++++++++--- internal/src/pin_data.rs | 107 +++++++++++++++++++--- tests/cfgs.rs | 83 ++++++++++++++++- tests/ui/expand/many_generics.expanded.rs | 15 ++- tests/ui/expand/pin-data.expanded.rs | 10 +- tests/ui/expand/pinned_drop.expanded.rs | 10 +- 6 files changed, 262 insertions(+), 45 deletions(-) diff --git a/internal/src/init.rs b/internal/src/init.rs index b65c52fc..17a20f4b 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -80,6 +80,15 @@ fn member_binding(member: &Member) -> Option { } } +fn cfg_condition(attrs: &[Attribute]) -> Option { + let cfgs: Vec<_> = attrs + .iter() + .filter(|attr| attr.path().is_ident("cfg")) + .filter_map(|attr| attr.parse_args::().ok()) + .collect(); + (!cfgs.is_empty()).then(|| quote!(all(#(#cfgs),*))) +} + enum InitializerAttribute { DefaultError(DefaultErrorAttribute), } @@ -434,18 +443,69 @@ fn parse_paren_initializer(input: syn::parse::ParseStream<'_>) -> syn::Result<(S let content; let paren_token = parenthesized!(content in input); let tuple_fields = content.parse_terminated(TupleInitializerField::parse, Token![,])?; + let tuple_fields: Vec<_> = tuple_fields.into_iter().collect(); + let cfg_conditions: Vec<_> = tuple_fields + .iter() + .map(|field| cfg_condition(&field.attrs)) + .collect(); + let conditional_fields: Vec<_> = cfg_conditions + .iter() + .enumerate() + .filter_map(|(index, cfg)| cfg.as_ref().map(|cfg| (index, cfg))) + .collect(); + let mut fields = Punctuated::new(); - for (index, tuple_field) in tuple_fields.into_iter().enumerate() { - fields.push(InitializerField { - attrs: tuple_field.attrs, - kind: InitializerKind::Value { - member: Member::Unnamed(Index { - index: index as u32, - span: tuple_field.value.span(), - }), - value: Some((Token![:](tuple_field.value.span()), tuple_field.value)), - }, - }); + + // Tuple constructor arguments are reindexed by rustc after cfg stripping. Since the proc macro + // cannot evaluate user cfgs, generate one initializer sequence for every cfg combination. + for combination in 0..(1usize << conditional_fields.len()) { + let cfgs: Vec<_> = conditional_fields + .iter() + .enumerate() + .map(|(bit, (_, cfg))| { + if combination & (1 << bit) == 0 { + quote!(not(#cfg)) + } else { + quote!(#cfg) + } + }) + .collect(); + let combination_attr: Option = + (!cfgs.is_empty()).then(|| parse_quote!(#[cfg(all(#(#cfgs),*))])); + let mut active_index = 0u32; + + for (index, tuple_field) in tuple_fields.iter().enumerate() { + let included = match cfg_conditions[index] { + None => true, + Some(_) => { + let bit = conditional_fields + .iter() + .position(|(field_index, _)| *field_index == index) + .expect("cfg-bearing field must be present"); + combination & (1 << bit) != 0 + } + }; + if !included { + continue; + } + + let mut attrs = tuple_field.attrs.clone(); + attrs.extend(combination_attr.clone()); + fields.push(InitializerField { + attrs, + kind: InitializerKind::Value { + member: Member::Unnamed(Index { + index: active_index, + span: tuple_field.value.span(), + }), + value: Some(( + Token![:](tuple_field.value.span()), + tuple_field.value.clone(), + )), + }, + }); + active_index += 1; + } } Ok((paren_token.span.close(), fields)) } diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index 1834d894..9830d94d 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -7,8 +7,8 @@ use syn::{ parse_quote, parse_quote_spanned, spanned::Spanned, visit_mut::VisitMut, - Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility, - WhereClause, + Attribute, Field, Fields, Generics, Ident, Index, Item, Member, Meta, PathSegment, Type, + TypePath, Visibility, WhereClause, }; use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; @@ -56,6 +56,15 @@ fn member_display_name(member: &Member) -> String { } } +fn cfg_condition(attrs: &[Attribute]) -> Option { + let cfgs: Vec<_> = attrs + .iter() + .filter(|attr| attr.path().is_ident("cfg")) + .filter_map(|attr| attr.parse_args::().ok()) + .collect(); + (!cfgs.is_empty()).then(|| quote!(all(#(#cfgs),*))) +} + pub(crate) fn pin_data( args: Args, input: Item, @@ -309,6 +318,12 @@ fn generate_projections( .map(|f| format!(" - {}", member_display_name(&f.member))); let docs = format!(" Pin-projections of [`{ident}`]"); + let needs_unreachable_allow = is_tuple_struct + && fields + .iter() + .any(|f| cfg_condition(&f.field.attrs).is_some()); + let unreachable_allow = needs_unreachable_allow.then(|| quote!(#[allow(unreachable_code)])); + let (projection_def, projection_init) = if !is_tuple_struct { let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields .iter() @@ -368,9 +383,12 @@ fn generate_projections( let mut fields_decl = Vec::new(); let mut field_bindings = Vec::new(); let mut field_projections = Vec::new(); + let mut cfg_conditions = Vec::new(); + for (index, field) in fields.iter().enumerate() { let Field { vis, ty, attrs, .. } = &field.field; let binding = format_ident!("__field_{index}"); + let cfg_condition = cfg_condition(attrs); if field.pinned { fields_decl.push(quote!( @@ -389,8 +407,79 @@ fn generate_projections( field_projections.push(quote!(#binding,)); } field_bindings.push(quote!(ref mut #binding,)); + cfg_conditions.push(cfg_condition); } + let conditional_fields: Vec<_> = cfg_conditions + .iter() + .enumerate() + .filter_map(|(index, cfg)| cfg.as_ref().map(|cfg| (index, cfg))) + .collect(); + let combinations = 1usize << conditional_fields.len(); + let mut projection_bodies = Vec::new(); + + // We cannot evaluate user cfgs inside the proc macro. Instead, generate one arm per + // cfg combination and let rustc keep the active tuple pattern. + for combination in 0..combinations { + let cfgs: Vec<_> = conditional_fields + .iter() + .enumerate() + .map(|(bit, (_, cfg))| { + if combination & (1 << bit) == 0 { + quote!(not(#cfg)) + } else { + quote!(#cfg) + } + }) + .collect(); + let cfg_attr = (!cfgs.is_empty()).then(|| quote!(#[cfg(all(#(#cfgs),*))])); + + let mut patterns = Vec::new(); + let mut projections = Vec::new(); + for (index, binding) in field_bindings.iter().enumerate() { + let included = match cfg_conditions[index] { + None => true, + Some(_) => { + let bit = conditional_fields + .iter() + .position(|(field_index, _)| *field_index == index) + .expect("cfg-bearing field must be present"); + combination & (1 << bit) != 0 + } + }; + if included { + patterns.push(binding); + projections.push(&field_projections[index]); + } + } + + projection_bodies.push(quote! { + #cfg_attr + { + let #ident(#(#patterns)*) = *#this; + return #projection( + #(#projections)* + ::core::marker::PhantomData, + ); + } + }); + } + let projection_init = if conditional_fields.is_empty() { + quote! {{ + let #ident(#(#field_bindings)*) = *#this; + #projection( + #(#field_projections)* + ::core::marker::PhantomData, + ) + }} + } else { + quote! {{ + #(#projection_bodies)* + #[allow(unreachable_code)] + ::core::unreachable!() + }} + }; + ( quote! { #[doc = #docs] @@ -401,13 +490,7 @@ fn generate_projections( ::core::marker::PhantomData<&'__pin mut ()>, ) #whr; }, - quote! {{ - let #ident(#(#field_bindings)*) = *#this; - #projection( - #(#field_projections)* - ::core::marker::PhantomData, - ) - }}, + projection_init, ) }; @@ -425,6 +508,7 @@ fn generate_projections( /// These fields are **not** structurally pinned: #(#[doc = #not_structurally_pinned_fields_docs])* #[inline] + #unreachable_allow #vis fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> #projection #ty_generics_with_pin_lt { @@ -465,9 +549,8 @@ fn generate_the_pin_data( /// # Safety /// /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + /// - The field is properly aligned. + /// - The field points to uninitialized and exclusively accessed memory. #(#attrs)* #[inline(always)] #vis unsafe fn #field_name( diff --git a/tests/cfgs.rs b/tests/cfgs.rs index f1be1bc2..6b39bbbe 100644 --- a/tests/cfgs.rs +++ b/tests/cfgs.rs @@ -1,4 +1,6 @@ -use pin_init::{pin_data, pin_init, PinInit}; +use core::{marker::PhantomPinned, pin::Pin}; + +use pin_init::{pin_data, pin_init, stack_pin_init, PinInit}; #[pin_data] pub struct Struct { @@ -21,9 +23,88 @@ impl Struct { struct Field {} +fn assert_pinned(_: Pin<&mut T>) {} + #[pin_data] pub struct Struct2 { // Test for cases where the type is not even defined when cfg is not satisfied. #[cfg(any())] non_exist: NonExistentType, } + +#[pin_data] +pub struct TupleStruct(#[cfg(any())] HiddenField, Field, #[pin] PhantomPinned); + +impl TupleStruct { + pub fn new() -> impl PinInit { + pin_init!(Self(Field {}, PhantomPinned)) + } +} + +#[allow(dead_code)] +struct HiddenField; + +#[test] +fn tuple_struct_reindexes_cfgd_out_fields() { + stack_pin_init!(let value = TupleStruct::new()); + let projected = value.as_mut().project(); + let _ = projected.0; + assert_pinned(projected.1); +} + +#[pin_data] +pub struct ConstructorCfgTuple(#[cfg(any())] HiddenField, Field, #[pin] PhantomPinned); + +impl ConstructorCfgTuple { + pub fn new() -> impl PinInit { + pin_init!(Self( + #[cfg(any())] + HiddenField, + Field {}, + PhantomPinned + )) + } +} + +#[test] +fn tuple_constructor_reindexes_cfgd_out_arguments() { + stack_pin_init!(let value = ConstructorCfgTuple::new()); + let projected = value.as_mut().project(); + let _ = projected.0; + assert_pinned(projected.1); +} + +#[pin_data] +pub struct FeatureTupleStruct( + #[cfg(not(feature = "std"))] HiddenField, + Field, + #[pin] PhantomPinned, +); + +impl FeatureTupleStruct { + pub fn new() -> impl PinInit { + pin_init!(Self( + #[cfg(not(feature = "std"))] + HiddenField, + Field {}, + PhantomPinned + )) + } +} + +#[test] +fn tuple_struct_reindexes_feature_cfgd_out_fields() { + stack_pin_init!(let value = FeatureTupleStruct::new()); + let projected = value.as_mut().project(); + #[cfg(feature = "std")] + { + let _ = projected.0; + assert_pinned(projected.1); + } + #[cfg(not(feature = "std"))] + { + let _ = projected.0; + let _ = projected.1; + assert_pinned(projected.2); + } +} diff --git a/tests/ui/expand/many_generics.expanded.rs b/tests/ui/expand/many_generics.expanded.rs index 1b0642fb..228d589c 100644 --- a/tests/ui/expand/many_generics.expanded.rs +++ b/tests/ui/expand/many_generics.expanded.rs @@ -95,9 +95,8 @@ const _: () = { /// # Safety /// /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + /// - The field is properly aligned. + /// - The field points to uninitialized and exclusively accessed memory. #[inline(always)] unsafe fn array( self, @@ -111,9 +110,8 @@ const _: () = { /// # Safety /// /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + /// - The field is properly aligned. + /// - The field points to uninitialized and exclusively accessed memory. #[inline(always)] unsafe fn r( self, @@ -127,9 +125,8 @@ const _: () = { /// # Safety /// /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + /// - The field is properly aligned. + /// - The field points to uninitialized and exclusively accessed memory. #[inline(always)] unsafe fn _pin( self, diff --git a/tests/ui/expand/pin-data.expanded.rs b/tests/ui/expand/pin-data.expanded.rs index e79642cd..bc8603e5 100644 --- a/tests/ui/expand/pin-data.expanded.rs +++ b/tests/ui/expand/pin-data.expanded.rs @@ -59,9 +59,8 @@ const _: () = { /// # Safety /// /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + /// - The field is properly aligned. + /// - The field points to uninitialized and exclusively accessed memory. #[inline(always)] unsafe fn array( self, @@ -75,9 +74,8 @@ const _: () = { /// # Safety /// /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + /// - The field is properly aligned. + /// - The field points to uninitialized and exclusively accessed memory. #[inline(always)] unsafe fn _pin( self, diff --git a/tests/ui/expand/pinned_drop.expanded.rs b/tests/ui/expand/pinned_drop.expanded.rs index 0f1893c2..f0a3f823 100644 --- a/tests/ui/expand/pinned_drop.expanded.rs +++ b/tests/ui/expand/pinned_drop.expanded.rs @@ -59,9 +59,8 @@ const _: () = { /// # Safety /// /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + /// - The field is properly aligned. + /// - The field points to uninitialized and exclusively accessed memory. #[inline(always)] unsafe fn array( self, @@ -75,9 +74,8 @@ const _: () = { /// # Safety /// /// - `slot` is valid and properly aligned. - /// - `(*slot).#field_name` is properly aligned. - /// - `(*slot).#field_name` points to uninitialized and exclusively accessed - /// memory. + /// - The field is properly aligned. + /// - The field points to uninitialized and exclusively accessed memory. #[inline(always)] unsafe fn _pin( self,