From 8e7879a9b45d644b37a030a20c5b53512f04793a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 2 Sep 2026 18:01:06 +0100 Subject: [PATCH 1/4] internal: extract utility code to new module Create a new `util.rs` to host utility code that are generic and can be shared by multiple macros. Signed-off-by: Gary Guo --- internal/src/lib.rs | 1 + internal/src/pin_data.rs | 17 +++++------------ internal/src/util.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 internal/src/util.rs diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 60d5093f..4d8ff864 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -18,6 +18,7 @@ mod diagnostics; mod init; mod pin_data; mod pinned_drop; +mod util; mod zeroable; #[proc_macro_attribute] diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index ff194d27..074bc6b3 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -10,7 +10,10 @@ use syn::{ Field, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, }; -use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + util::*, +}; pub(crate) mod kw { syn::custom_keyword!(PinnedDrop); @@ -81,21 +84,11 @@ pub(crate) fn pin_data( // // We need to perform this after parsing so we can reliably detect field cfgs. for (field_idx, field) in struct_.fields.iter_mut().enumerate() { - let cfg: Vec<_> = field - .attrs - .iter() - .filter(|a| a.path().is_ident("cfg")) - .map(|a| { - a.parse_args::() - .expect("parse as token stream cannot fail") - }) - .collect(); - + let cfg = field.attrs.extract_cfg_attrs(); if cfg.is_empty() { continue; } - field.attrs.retain(|a| !a.path().is_ident("cfg")); let cfg_true_struct = quote!(#struct_); let punctuated = match &mut struct_.fields { diff --git a/internal/src/util.rs b/internal/src/util.rs new file mode 100644 index 00000000..ed18ab7d --- /dev/null +++ b/internal/src/util.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro2::TokenStream; +use syn::Attribute; + +pub(crate) trait AttrListExt { + fn extract_cfg_attrs(&mut self) -> Vec; +} + +impl AttrListExt for Vec { + fn extract_cfg_attrs(&mut self) -> Vec { + let cfg: Vec<_> = self + .iter() + .filter(|a| a.path().is_ident("cfg")) + .map(|a| { + a.parse_args::() + .expect("parse as token stream cannot fail") + }) + .collect(); + + if !cfg.is_empty() { + self.retain(|a| !a.path().is_ident("cfg")); + } + + cfg + } +} From 2742f1b2f100cd53bf2b721ea8bcbe47e38ba91a Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 28 Aug 2026 14:22:00 +0300 Subject: [PATCH 2/4] internal: pin_data: support tuple struct projections `#[pin_data]` rejects tuple structs because it assumes every field has a name, which it uses for the projection field, the `__Unpin` field and the pin-data accessor. Identify fields by `syn::Member` instead, so that tuple fields are referred to by their index in generated field accesses. The names that generated items still need are derived from the index as `_0`, `_1`, etc. The projection of a tuple struct is a tuple struct itself, so projected fields are accessed with the same `.0`, `.1` syntax as on the input type rather than through synthesised names. Signed-off-by: Mohamad Alsadhan [ Moved utility code to util.rs as extension trait - Gary ] Signed-off-by: Gary Guo --- CHANGELOG.md | 1 + internal/src/pin_data.rs | 112 +++++++++----- internal/src/util.rs | 31 +++- src/lib.rs | 23 +++ tests/attrs.rs | 9 ++ .../tuple_struct_missing_pin_phantom.rs | 8 + .../tuple_struct_missing_pin_phantom.stderr | 13 ++ .../tuple_struct_pinned_field_not_unpin.rs | 11 ++ ...tuple_struct_pinned_field_not_unpin.stderr | 26 ++++ tests/ui/expand/tuple_struct.expanded.rs | 142 ++++++++++++++++++ tests/ui/expand/tuple_struct.rs | 7 + 11 files changed, 347 insertions(+), 36 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_field_not_unpin.rs create mode 100644 tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_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 b91d884b..87b6b30d 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 +- `#[pin_data]` now supports tuple structs. - `[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/pin_data.rs b/internal/src/pin_data.rs index 074bc6b3..8cd9bf13 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, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, + Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility, + WhereClause, }; use crate::{ @@ -49,6 +50,7 @@ impl ToTokens for Args { struct FieldInfo<'a> { field: &'a Field, + member: Member, pinned: bool, } @@ -129,10 +131,12 @@ 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_count = len - field.attrs.len(); @@ -144,23 +148,30 @@ pub(crate) fn pin_data( !field.attrs.iter().any(|a| a.path().is_ident("cfg")), "cfgs should be all resolved at this point" ); + 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: pinned_count != 0, } }) .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", + field.member.display_name(), ), ); } @@ -168,8 +179,13 @@ 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 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, &fields); @@ -231,7 +247,7 @@ fn generate_unpin_impl( unreachable!() }; let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| { - let ident = f.field.ident.as_ref().unwrap(); + let ident = f.member.as_ident(); let ty = &f.field.ty; quote!( #ident: #ty @@ -313,6 +329,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(); @@ -325,28 +342,32 @@ fn generate_projections( let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields .iter() .map(|field| { - let Field { vis, ident, ty, .. } = &field.field; + let Field { vis, ty, .. } = &field.field; + let member = &field.member; + // The projection of a tuple struct is a tuple struct itself, so its fields are + // positional and must not be named. + let name = (!is_tuple_struct).then(|| { + let ident = field.member.as_ident(); + quote!(#ident:) + }); - let ident = ident - .as_ref() - .expect("only structs with named fields are supported"); if field.pinned { ( quote!( - #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, + #vis #name ::core::pin::Pin<&'__pin mut #ty>, ), quote!( // SAFETY: this field is structurally pinned. - #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, + #name unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) }, ), ) } else { ( quote!( - #vis #ident: &'__pin mut #ty, + #vis #name &'__pin mut #ty, ), quote!( - #ident: &mut #this.#ident, + #name &mut #this.#member, ), ) } @@ -355,24 +376,52 @@ fn generate_projections( let structurally_pinned_fields_docs = fields .iter() .filter(|f| f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - {}", f.member.display_name())); let not_structurally_pinned_fields_docs = fields .iter() .filter(|f| !f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - {}", f.member.display_name())); let docs = format!(" Pin-projections of [`{ident}`]"); + let (projection_def, projection_init) = if is_tuple_struct { + ( + quote! { + #vis struct #projection #generics_with_pin_lt ( + #(#fields_decl)* + ::core::marker::PhantomData<&'__pin mut ()>, + ) #whr; + }, + quote! { + #projection( + #(#fields_proj)* + ::core::marker::PhantomData, + ) + }, + ) + } else { + ( + quote! { + #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, + } + }, + ) + }; quote! { #[doc = #docs] // Allow `non_snake_case` since the same warning will be emitted on // the struct definition. #[allow(dead_code, non_snake_case)] #[doc(hidden)] - #vis struct #projection #generics_with_pin_lt - #whr - { - #(#fields_decl)* - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, - } + #projection_def impl #impl_generics #ident #ty_generics #whr @@ -390,10 +439,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 } } } @@ -414,11 +460,9 @@ fn generate_the_pin_data( let field_accessors = fields .iter() .map(|f| { - let Field { vis, ident, ty, .. } = f.field; - - let field_name = ident - .as_ref() - .expect("only structs with named fields are supported"); + let Field { vis, ty, .. } = f.field; + let field_name = f.member.as_ident(); + let member = &f.member; let pin_marker = if f.pinned { quote!(Pinned) } else { @@ -443,7 +487,7 @@ fn generate_the_pin_data( // - 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) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#member) } } } }) diff --git a/internal/src/util.rs b/internal/src/util.rs index ed18ab7d..ed2c78f0 100644 --- a/internal/src/util.rs +++ b/internal/src/util.rs @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use proc_macro2::TokenStream; -use syn::Attribute; +use proc_macro2::{Ident, TokenStream}; +use quote::format_ident; +use syn::{Attribute, Index, Member}; pub(crate) trait AttrListExt { fn extract_cfg_attrs(&mut self) -> Vec; @@ -25,3 +26,29 @@ impl AttrListExt for Vec { cfg } } + +pub(crate) trait MemberExt { + /// Returns an identifier for the member. + /// + /// Tuple fields have no name of their own, so they are named `_0`, `_1`, ... instead. + fn as_ident(&self) -> Ident; + + /// Obtain a display name for the member in diagnostics. + fn display_name(&self) -> String; +} + +impl MemberExt for Member { + fn as_ident(&self) -> Ident { + match self { + Member::Named(ident) => ident.clone(), + Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), + } + } + + fn display_name(&self) -> String { + match self { + Member::Named(ident) => format!("`{ident}`"), + Member::Unnamed(Index { index, .. }) => format!("index `{index}`"), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f1463be9..6bf42c9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -304,6 +304,9 @@ pub use alloc::InPlaceInit; /// This macro enables the use of the [`pin_init!`] macro. When pin-initializing a `struct`, /// then `#[pin]` directs the type of initializer that is required. /// +/// Tuple structs are supported as well. Their fields have no names, so the generated projection +/// is a tuple struct too and its fields are accessed by index. +/// /// If your `struct` implements `Drop`, then you need to add `PinnedDrop` as arguments to this /// macro, and change your `Drop` implementation to `PinnedDrop` annotated with /// `#[`[`macro@pinned_drop`]`]`, since dropping pinned values requires extra care. @@ -327,6 +330,26 @@ pub use alloc::InPlaceInit; /// } /// ``` /// +/// The same as a tuple struct, projected by index: +/// +/// ``` +/// # #![feature(allocator_api)] +/// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*; +/// use core::pin::Pin; +/// use pin_init::pin_data; +/// +/// enum Command { +/// /* ... */ +/// } +/// +/// #[pin_data] +/// struct DriverData(#[pin] CMutex>, Box<[u8; 1024 * 1024]>); +/// +/// fn queue(data: Pin<&mut DriverData>) -> Pin<&mut CMutex>> { +/// data.project().0 +/// } +/// ``` +/// /// ``` /// # #![feature(allocator_api)] /// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*; diff --git a/tests/attrs.rs b/tests/attrs.rs index d7a7e298..f2ec7663 100644 --- a/tests/attrs.rs +++ b/tests/attrs.rs @@ -13,8 +13,17 @@ struct Foo { member: u8, } +#[pin_data] +#[derive(serde::Serialize)] +struct Tuple( + #[pin] + #[serde()] + u8, +); + #[test] fn test_attribute() { stack_pin_init!(let p = init!(Foo { member: 0 })); println!("{}", p.member); + let _ = Tuple(0); } 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_field_not_unpin.rs b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs new file mode 100644 index 00000000..1500cc44 --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs @@ -0,0 +1,11 @@ +use core::marker::PhantomPinned; +use pin_init::*; + +#[pin_data] +struct Tuple(#[pin] PhantomPinned, T); + +fn assert_unpin() {} + +fn main() { + assert_unpin::>(); +} diff --git a/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.stderr b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.stderr new file mode 100644 index 00000000..1cbfd3fa --- /dev/null +++ b/tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.stderr @@ -0,0 +1,26 @@ +error[E0277]: `PhantomPinned` cannot be unpinned + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs:10:20 + | +10 | assert_unpin::>(); + | ^^^^^^^^^^^^ within `__Unpin<'_, usize>`, 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 `__Unpin<'_, usize>` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs:4:1 + | + 4 | #[pin_data] + | ^^^^^^^^^^^ +note: required for `Tuple` to implement `Unpin` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs:4:1 + | + 4 | #[pin_data] + | ^^^^^^^^^^^ unsatisfied trait bound introduced here + 5 | struct Tuple(#[pin] PhantomPinned, T); + | ^^^^^^^^ +note: required by a bound in `assert_unpin` + --> tests/ui/compile-fail/pin_data/tuple_struct_pinned_field_not_unpin.rs:7:20 + | + 7 | 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..b27de99b --- /dev/null +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -0,0 +1,142 @@ +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, non_snake_case)] +#[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) }; + FooProjection( + &mut this.0, + unsafe { ::core::pin::Pin::new_unchecked(&mut this.1) }, + &mut this.2, + ::core::marker::PhantomData, + ) + } +} +const _: () = { + #[doc(hidden)] + struct __ThePinData<'a, T: Copy, const N: usize> { + __phantom: ::pin_init::__internal::PhantomInvariant>, + } + impl<'a, T: Copy, const N: usize> ::core::clone::Clone for __ThePinData<'a, T, N> { + #[inline] + fn clone(&self) -> Self { + *self + } + } + impl<'a, T: Copy, const N: usize> ::core::marker::Copy for __ThePinData<'a, T, N> {} + #[allow(dead_code)] + 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 + } + /// # Safety + /// + /// - `slot` is valid and properly aligned. + /// - `(*slot).#field_name` is properly aligned. + /// - `(*slot).#field_name` points to uninitialized and exclusively accessed + /// memory. + #[allow(non_snake_case)] + #[inline(always)] + unsafe fn _0( + self, + slot: *mut Foo<'a, T, N>, + ) -> ::pin_init::__internal::Slot< + ::pin_init::__internal::Unpinned, + &'a mut [T; N], + > { + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).0) } + } + /// # Safety + /// + /// - `slot` is valid and properly aligned. + /// - `(*slot).#field_name` is properly aligned. + /// - `(*slot).#field_name` points to uninitialized and exclusively accessed + /// memory. + #[allow(non_snake_case)] + #[inline(always)] + unsafe fn _1( + self, + slot: *mut Foo<'a, T, N>, + ) -> ::pin_init::__internal::Slot< + ::pin_init::__internal::Pinned, + PhantomPinned, + > { + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).1) } + } + /// # Safety + /// + /// - `slot` is valid and properly aligned. + /// - `(*slot).#field_name` is properly aligned. + /// - `(*slot).#field_name` points to uninitialized and exclusively accessed + /// memory. + #[allow(non_snake_case)] + #[inline(always)] + unsafe fn _2( + self, + slot: *mut Foo<'a, T, N>, + ) -> ::pin_init::__internal::Slot<::pin_init::__internal::Unpinned, usize> { + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).2) } + } + } + unsafe impl<'a, T: Copy, const N: usize> ::pin_init::__internal::HasPinData + for Foo<'a, T, N> { + type PinData = __ThePinData<'a, T, N>; + #[inline] + unsafe fn __pin_data() -> Self::PinData { + __ThePinData { + __phantom: ::pin_init::__internal::PhantomInvariant::new(), + } + } + } + #[allow(dead_code, non_snake_case)] + 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 {} + impl MustNotImplDrop for T {} + impl<'a, T: Copy, const N: usize> MustNotImplDrop for Foo<'a, T, N> {} + 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 af9493a1889a788da5f0a0d92ec22fe172ea8102 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 28 Aug 2026 14:27:03 +0300 Subject: [PATCH 3/4] internal: init: support tuple structs in `[pin_]init!` Extend the initializer syntax so that a field can be named by an index, addressing tuple struct fields the same way a struct expression does: pin_init!(Foo { 0: value, 1 <- initializer }) Tuple fields are not exposed by a `let` binding to the fields after them, since they have no name to bind; `_0` would shadow a user variable. Signed-off-by: Mohamad Alsadhan [ Fixed incorrect index calculation and cleaned up the code - Gary ] Signed-off-by: Gary Guo --- CHANGELOG.md | 3 +- internal/src/init.rs | 115 +++++++----- src/lib.rs | 32 +++- tests/cfgs.rs | 25 ++- tests/tuple_struct.rs | 167 ++++++++++++++++++ .../compile-fail/init/no_tuple_shorthand.rs | 8 + .../init/no_tuple_shorthand.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 | 33 ++++ .../compile-fail/init/tuple_missing_field.rs | 9 + .../init/tuple_missing_field.stderr | 11 ++ tests/ui/expand/tuple_struct.expanded.rs | 61 ++++++- tests/ui/expand/tuple_struct.rs | 9 +- 15 files changed, 446 insertions(+), 56 deletions(-) create mode 100644 tests/tuple_struct.rs 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/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 87b6b30d..cc4355ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `#[pin_data]` now supports tuple structs. +- Tuple structs are now supported. `[pin_]init!` can only be used to create + them with struct syntax, e.g. `init!(Foo { 0: value, 1 <- initializer })`. - `[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 fd0b5ea4..5920bb28 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -8,10 +8,13 @@ use syn::{ parse_quote, punctuated::Punctuated, spanned::Spanned, - token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, + token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, LitInt, Member, Path, Token, Type, }; -use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + util::*, +}; pub(crate) struct Initializer { attrs: Vec, @@ -36,11 +39,11 @@ struct InitializerField { enum InitializerKind { Value { - ident: Ident, + member: Member, value: Option<(Token![:], Expr)>, }, Init { - ident: Ident, + member: Member, _left_arrow_token: Token![<-], value: Expr, }, @@ -52,9 +55,9 @@ 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, } } @@ -229,9 +232,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, .. } => { let stmt = &block.stmts; res.extend(quote! { @@ -243,27 +246,28 @@ fn init_fields( continue; } }; + let ident = member.as_ident(); 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. + // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. + // - `make_field_check` prevents `#member` from being used twice, therefore + // `(*slot).#member` is exclusively accessed and has not been initialized. (unsafe { #data.#ident(#slot) }) } } 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. + // - `&raw mut (*slot).#member` is valid. + // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. + // - `make_field_check` prevents `#member` from being used twice, therefore + // `(*slot).#member` 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 ) }) } @@ -273,11 +277,11 @@ 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)) - .unwrap_or_else(|| quote!(#ident)); + .unwrap_or_else(|| quote!(#member)); quote! { #(#attrs)* @@ -294,14 +298,23 @@ fn init_fields( InitializerKind::Code { .. } => unreachable!(), }; + // A tuple field has no name that could be bound here (the `_0` identifiers are considered + // implementation detail and not user-facing). + let binding = match member { + Member::Named(ident) => quote! { + #(#cfgs)* + // Allow `non_snake_case` since the same warning is going to be reported for the + // struct field. + #[allow(unused_variables, non_snake_case)] + let #ident = #guard.let_binding(); + }, + Member::Unnamed(_) => quote!(), + }; + res.extend(quote! { #init - #(#cfgs)* - // Allow `non_snake_case` since the same warning is going to be reported for the struct - // field. - #[allow(unused_variables, non_snake_case)] - let #ident = #guard.let_binding(); + #binding }); guards.push(guard); @@ -326,9 +339,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! { @@ -376,7 +389,8 @@ impl Parse for Initializer { let lh = content.lookahead1(); if lh.peek(End) || lh.peek(Token![..]) { break; - } else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Token![#]) { + } 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) { @@ -450,31 +464,36 @@ impl Parse for InitializerField { impl Parse for InitializerKind { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let lh = input.lookahead1(); - if lh.peek(Token![_]) { - Ok(Self::Code { + let member = if lh.peek(Token![_]) { + return Ok(Self::Code { _underscore_token: input.parse()?, _colon_token: input.parse()?, block: input.parse()?, + }); + } else if lh.peek(Ident) || lh.peek(LitInt) { + input.parse::()? + } else { + return Err(lh.error()); + }; + + let lh = input.lookahead1(); + if lh.peek(Token![<-]) { + Ok(Self::Init { + member, + _left_arrow_token: input.parse()?, + value: input.parse()?, + }) + } else if lh.peek(Token![:]) { + Ok(Self::Value { + member, + value: Some((input.parse()?, input.parse()?)), + }) + } else if matches!(member, Member::Named(_)) && (lh.peek(Token![,]) || lh.peek(End)) { + // Short-hand syntax, available for named fields only. + Ok(Self::Value { + member, + value: None, }) - } else if lh.peek(Ident) { - let ident = input.parse()?; - let lh = input.lookahead1(); - if lh.peek(Token![<-]) { - Ok(Self::Init { - ident, - _left_arrow_token: input.parse()?, - value: input.parse()?, - }) - } else if lh.peek(Token![:]) { - Ok(Self::Value { - ident, - value: Some((input.parse()?, input.parse()?)), - }) - } else if lh.peek(Token![,]) || lh.peek(End) { - Ok(Self::Value { ident, value: None }) - } else { - Err(lh.error()) - } } else { Err(lh.error()) } diff --git a/src/lib.rs b/src/lib.rs index 6bf42c9e..fc34cfd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -592,7 +592,7 @@ macro_rules! stack_try_pin_init { }; } -/// Construct an in-place, fallible pinned initializer for `struct`s. +/// Construct an in-place, fallible pinned initializer for structs, including tuple structs. /// /// The error type defaults to [`Infallible`]; if you need a different one, write `? Error` at the /// end, after the struct initializer. @@ -626,6 +626,28 @@ macro_rules! stack_try_pin_init { /// # Box::pin_init(demo()).unwrap(); /// ``` /// +/// The fields of a tuple struct are addressed by their index: +/// +/// ```rust +/// # use pin_init::*; +/// # use core::pin::Pin; +/// #[pin_data] +/// struct Pair(usize, Bar); +/// +/// #[pin_data] +/// struct Bar { +/// x: u32, +/// } +/// +/// # fn demo() -> impl PinInit { +/// let initializer = pin_init!(Pair { +/// 0: 42, +/// 1 <- Bar { x: 64 }, +/// }); +/// # initializer } +/// # Box::pin_init(demo()).unwrap(); +/// ``` +/// /// Arbitrary Rust expressions can be used to set the value of a variable. /// /// The fields are initialized in the order that they appear in the initializer. So it is possible @@ -744,9 +766,11 @@ macro_rules! stack_try_pin_init { /// /// # Syntax /// -/// As already mentioned in the examples above, inside of `pin_init!` a `struct` initializer with -/// the following modifications is expected: +/// As already mentioned in the examples above, inside of `pin_init!` a struct initializer with the +/// following modifications is expected: /// - Fields that you want to initialize in-place have to use `<-` instead of `:`. +/// - Tuple struct fields are named by their index, as in `0: value` or `0 <- initializer`. They +/// are not exposed by a `let` binding, since they have no name to bind. /// - You can use `_: { /* run any user-code here */ },` anywhere where you can place fields in /// order to run arbitrary code. /// - In front of the initializer you can write `&this in` to have access to a [`NonNull`] @@ -785,7 +809,7 @@ macro_rules! stack_try_pin_init { /// [`NonNull`]: core::ptr::NonNull pub use pin_init_internal::pin_init; -/// Construct an in-place, fallible initializer for `struct`s. +/// Construct an in-place, fallible initializer for structs, including tuple structs. /// /// This macro defaults the error to [`Infallible`]; if you need a different one, write `? Error` /// at the end, after the struct initializer. diff --git a/tests/cfgs.rs b/tests/cfgs.rs index f1be1bc2..ffa2e862 100644 --- a/tests/cfgs.rs +++ b/tests/cfgs.rs @@ -1,4 +1,4 @@ -use pin_init::{pin_data, pin_init, PinInit}; +use pin_init::{pin_data, pin_init, stack_pin_init, PinInit}; #[pin_data] pub struct Struct { @@ -27,3 +27,26 @@ pub struct Struct2 { #[cfg(any())] non_exist: NonExistentType, } + +#[pin_data] +pub struct TupleStruct(#[cfg(any())] Field, u32, u32); + +impl TupleStruct { + pub fn new() -> impl PinInit { + pin_init!(Self { + #[cfg(any())] + 0: Field, + // Disabled fields don't occupy an index! + 0: 10, + 1: 20, + }) + } +} + +#[test] +fn tuple_fields_cfg_renumber() { + stack_pin_init!(let value = TupleStruct::new()); + let proj = value.project(); + assert_eq!(*proj.0, 10); + assert_eq!(*proj.1, 20); +} diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs new file mode 100644 index 00000000..59bf1265 --- /dev/null +++ b/tests/tuple_struct.rs @@ -0,0 +1,167 @@ +#![cfg_attr(feature = "alloc", feature(allocator_api))] + +use core::{ + pin::Pin, + sync::atomic::{AtomicUsize, Ordering}, +}; +use pin_init::*; + +#[allow(unused_attributes)] +#[path = "../examples/mutex.rs"] +mod mutex; +use mutex::*; + +fn assert_pinned(_: &Pin<&mut T>) {} + +fn assert_unpin() {} + +#[pin_data] +struct TupleStruct(#[pin] CMutex, i32); + +#[test] +fn init_and_projection() { + stack_pin_init!(let tuple = pin_init!(TupleStruct:: { 0 <- CMutex::new(7), 1: 13 })); + + let projected = tuple.project(); + assert_pinned(&projected.0); + assert_eq!(*projected.0.as_ref().get_ref().lock(), 7); + assert_eq!(*projected.1, 13); +} + +#[pin_data] +struct Triple(i32, i32, i32); + +#[test] +fn init_without_pinning() { + stack_pin_init!(let triple = init!(Triple { 0: 37, 1: 41, 2: 43 })); + + assert_eq!(triple.as_ref().get_ref().0, 37); + assert_eq!(triple.as_ref().get_ref().1, 41); + assert_eq!(triple.as_ref().get_ref().2, 43); +} + +#[pin_data] +struct DualPinned(#[pin] CMutex, #[pin] CMutex, usize); + +#[test] +fn multi_pinned() { + 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(&projected.0); + assert_pinned(&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); +} + +#[pin_data] +struct GenericTuple<'a, T, const N: usize>(#[pin] CMutex<(&'a T, [u8; N])>, usize); + +#[test] +fn generics() { + 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(&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); +} + +#[pin_data] +struct TupleConst(#[pin] CMutex<[T; N]>, usize); + +#[test] +fn const_generics_turbofish() { + stack_pin_init!(let tuple = pin_init!(TupleConst:: { 0 <- CMutex::new([1, 2, 3]), 1: 9 })); + + let projected = tuple.as_mut().project(); + assert_pinned(&projected.0); + assert_eq!(*projected.0.as_ref().get_ref().lock(), [1, 2, 3]); + assert_eq!(*projected.1, 9); +} + +#[pin_data] +#[allow(dead_code)] +struct UnpinnedMutexTuple(CMutex, usize); + +#[test] +fn unpin_ignores_unpinned_non_unpin_field() { + assert_unpin::>(); +} + +#[pin_data(PinnedDrop)] +struct DropTuple(#[pin] CMutex, usize); + +static PINNED_DROP_TUPLE_DROPS: AtomicUsize = AtomicUsize::new(0); + +#[pinned_drop] +impl PinnedDrop for DropTuple { + fn drop(self: Pin<&mut Self>) { + PINNED_DROP_TUPLE_DROPS.fetch_add(1, Ordering::Relaxed); + } +} + +#[test] +fn pinned_drop_delegates_from_drop() { + PINNED_DROP_TUPLE_DROPS.store(0, Ordering::Relaxed); + { + stack_pin_init!(let _tuple = pin_init!(DropTuple { 0 <- CMutex::new(5usize), 1: 1 })); + } + assert_eq!(PINNED_DROP_TUPLE_DROPS.load(Ordering::Relaxed), 1); +} + +static FALLIBLE_TUPLE_DROPS: AtomicUsize = AtomicUsize::new(0); + +struct DropCounter; + +impl Drop for DropCounter { + fn drop(&mut self) { + FALLIBLE_TUPLE_DROPS.fetch_add(1, Ordering::Relaxed); + } +} + +#[derive(Debug)] +struct InitError; + +impl From for InitError { + fn from(error: core::convert::Infallible) -> Self { + match error {} + } +} + +fn fail() -> impl Init { + // SAFETY: The closure returns an error without touching the slot. + unsafe { init_from_closure(|_| Err(InitError)) } +} + +fn tuple_failing_init() -> impl PinInit, InitError> { + pin_init!(TupleStruct { + 0 <- CMutex::new(DropCounter), + 1 <- fail(), + }? InitError) +} + +#[test] +fn fallible_init_drops_initialized_fields() { + FALLIBLE_TUPLE_DROPS.store(0, Ordering::Relaxed); + stack_try_pin_init!(let tuple: TupleStruct = tuple_failing_init()); + assert!(matches!(tuple, Err(InitError))); + assert_eq!( + FALLIBLE_TUPLE_DROPS.load(core::sync::atomic::Ordering::Relaxed), + 1 + ); +} 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/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..f18d6e88 --- /dev/null +++ b/tests/ui/compile-fail/init/tuple_invalid_field.stderr @@ -0,0 +1,33 @@ +error[E0599]: no method named `_2` found for struct `__ThePinData` in the current scope + --> tests/ui/compile-fail/init/tuple_invalid_field.rs:7:13 + | +3 | #[pin_data] + | ----------- method `_2` not found for this struct +... +7 | let _ = pin_init!(Tuple { 0: 1, 1: 2, 2: 3 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `pin_init` (in Nightly builds, run with -Z macro-backtrace for more info) + +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 b27de99b..26a29b1d 100644 --- a/tests/ui/expand/tuple_struct.expanded.rs +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -139,4 +139,63 @@ 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)] + let _ = || unsafe { + let _ = &(*slot).0; + let _ = &(*slot).1; + let _ = &(*slot).2; + ::core::ptr::write( + slot, + Foo { + 0: loop {}, + 1: loop {}, + 2: loop {}, + }, + ) + }; + 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..f193bc18 100644 --- a/tests/ui/expand/tuple_struct.rs +++ b/tests/ui/expand/tuple_struct.rs @@ -4,4 +4,11 @@ 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, + }); +} From 5d04c89685b368a88704e1b1928621a719818187 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Wed, 2 Sep 2026 18:12:14 +0100 Subject: [PATCH 4/4] internal: init: support tuple struct constructor syntax A tuple struct whose fields are all set to a value reads better written like a call to its constructor than with the indices spelled out: pin_init!(Foo(value, value)) Parse the two forms into separate types and rewrite the constructor arguments into the indexed fields they are shorthand for, so that only the parser has to know about the second form. The arguments have no names, so they cannot use `<-`. Parse it anyway and reject it afterwards, which reports the position of every offending `<-` rather than stopping at the first one. `cfg` needs different treatment for tuple constructor syntax. As non-derive proc macros are invoked before cfg is resolved, the macro cannot know whether a field survives, and dropping a tuple field renumbers every field after it. That cannot be expressed by attaching a `cfg` attribute to the initializer of a single field. Thus, resolve tuple field cfgs up front instead, by generating two cfg-gated invocations of the macro with one field resolved in each. This is the approach of commit 3445a65dab60 ("internal: rework how `#[pin_data]` handles cfg"), and it is linear time because only one of the two branches is ever expanded. Struct expression syntax do not renumber, so using tuple structs with struct syntax can keep using the existing attribute-based handling. Suggested-by: Gary Guo Link: https://github.com/Rust-for-Linux/pin-init/pull/165 Signed-off-by: Mohamad Alsadhan Co-developed-by: Gary Guo # cfg expansion [ Use generics instead of separate types for normalization - Gary ] Signed-off-by: Gary Guo --- CHANGELOG.md | 4 +- internal/src/init.rs | 372 +++++++++++++++++- internal/src/lib.rs | 11 +- src/lib.rs | 16 + tests/cfg_explode.rs | 37 ++ tests/cfgs.rs | 9 + tests/tuple_struct.rs | 32 ++ .../compile-fail/init/no_tuple_paren_arrow.rs | 8 + .../init/no_tuple_paren_arrow.stderr | 5 + .../init/no_tuple_syntax_mixing.rs | 8 + .../init/no_tuple_syntax_mixing.stderr | 5 + tests/ui/expand/tuple_struct.expanded.rs | 58 +++ tests/ui/expand/tuple_struct.rs | 3 + 13 files changed, 542 insertions(+), 26 deletions(-) create mode 100644 tests/cfg_explode.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_syntax_mixing.rs create mode 100644 tests/ui/compile-fail/init/no_tuple_syntax_mixing.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index cc4355ba..3d9cf32d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Tuple structs are now supported. `[pin_]init!` can only be used to create - them with struct syntax, e.g. `init!(Foo { 0: value, 1 <- initializer })`. +- Tuple structs are now supported. For `[pin_]init!` , if pinning (`<-` syntax) is required, only + the struct syntax can be used, e.g. `init!(Foo { 0: value, 1 <- initializer })`. - `[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 5920bb28..ee6e67a1 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -1,12 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ - braced, + braced, parenthesized, parse::{End, Parse}, parse_quote, - punctuated::Punctuated, + punctuated::{Pair, Punctuated}, spanned::Spanned, token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, LitInt, Member, Path, Token, Type, }; @@ -16,14 +16,84 @@ use crate::{ util::*, }; -pub(crate) struct Initializer { +pub(crate) struct Initializer { attrs: Vec, this: Option, + kind: Kind, + error: Option<(Token![?], Type)>, +} + +pub(crate) struct InitExprStruct { path: Path, brace_token: token::Brace, fields: Punctuated, rest: Option<(Token![..], Expr)>, - error: Option<(Token![?], Type)>, +} + +pub(crate) struct InitExprTuple { + path: Path, + paren_token: token::Paren, + fields: Punctuated, +} + +pub(crate) enum InitExprKind { + Struct(InitExprStruct), + Tuple(InitExprTuple), +} + +struct InitTupleField { + attrs: Vec, + /// `<-` is not valid in constructor syntax; it is parsed anyway so that it can be rejected + /// with a proper diagnostic instead of a parse error. + left_arrow_token: Option, + value: Expr, +} + +impl InitExprTuple { + fn normalize(self) -> InitExprStruct { + let InitExprTuple { + path, + paren_token, + fields, + } = self; + InitExprStruct { + path, + brace_token: token::Brace { + span: paren_token.span, + }, + fields: fields + .into_pairs() + .enumerate() + .map(|(index, pair)| { + let (field, comma) = pair.into_tuple(); + let span = field.value.span(); + let field = InitializerField { + attrs: field.attrs, + kind: InitializerKind::Value { + member: Member::Unnamed(index.into()), + value: Some((Token![:](span), field.value)), + }, + }; + Pair::new(field, comma) + }) + .collect(), + rest: None, + } + } + + fn validate(&self, dcx: &mut DiagCtxt) -> Result<(), ErrorGuaranteed> { + let mut result = Ok(()); + for field in &self.fields { + if let Some(left_arrow_token) = &field.left_arrow_token { + result = Err(dcx.error( + left_arrow_token, + "`<-` is not supported in tuple constructor syntax; name the fields by index \ + instead, e.g. `Type { 0 <- initializer, 1: value }`", + )); + } + } + result + } } struct This { @@ -71,16 +141,100 @@ struct DefaultErrorAttribute { ty: Box, } -pub(crate) fn expand( +pub(crate) fn expand_with_cfg( + initializer: Initializer, + default_error: Option<&'static str>, + pinned: bool, + dcx: &mut DiagCtxt, +) -> Result { + let initializer = match initializer.kind { + InitExprKind::Tuple(expr) => { + expr.validate(dcx)?; + + let mut initializer = Initializer { + attrs: initializer.attrs, + this: initializer.this, + kind: expr, + error: initializer.error, + }; + + // Removing a tuple field renumbers every field after it, which cannot be expressed with a + // `cfg` attribute on the initializer of a single field. Therefore, resolve tuple field cfgs + // before continuing. Struct expression syntax uses explicit numbers, so there is no + // need to pre-expand them and we only need to emit their cfgs on generated code. + for (field_idx, field) in initializer.kind.fields.iter_mut().enumerate() { + let cfg = field.attrs.extract_cfg_attrs(); + + if cfg.is_empty() { + continue; + } + + let true_initializer = initializer.to_token_stream(); + initializer.kind.fields = initializer + .kind + .fields + .into_pairs() + .enumerate() + .filter(|&(index, _)| index != field_idx) + .map(|(_, pair)| pair) + .collect(); + + let false_initializer = &initializer; + + let macro_name = if pinned { + quote!(::pin_init::pin_init) + } else { + quote!(::pin_init::init) + }; + + // Resolve one field at a time until we've got no more tuple field cfgs. + // + // This is linear time because macro invocations with false cfg will not be expanded. + return Ok(quote! { + { + // Use `{}` delimiter here so semicolon is not required (which becomes unit type). + #[cfg(all(#(#cfg,)*))] + #macro_name! { #true_initializer } + + #[cfg(not(all(#(#cfg,)*)))] + #macro_name! { #false_initializer } + } + }); + } + + // No cfgs left, we can normalize the initializer to the struct kind. + Initializer { + attrs: initializer.attrs, + this: initializer.this, + kind: initializer.kind.normalize(), + error: initializer.error, + } + } + + InitExprKind::Struct(expr) => Initializer { + attrs: initializer.attrs, + this: initializer.this, + kind: expr, + error: initializer.error, + }, + }; + + expand(initializer, default_error, pinned, dcx) +} + +fn expand( Initializer { attrs, this, - path, - brace_token, - fields, - rest, + kind: + InitExprStruct { + path, + brace_token, + fields, + rest, + }, error, - }: Initializer, + }: Initializer, default_error: Option<&'static str>, pinned: bool, dcx: &mut DiagCtxt, @@ -99,7 +253,10 @@ 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( + brace_token.span.close(), + "expected `? ` after initializer", + ); parse_quote!(::core::convert::Infallible) } }, @@ -377,11 +534,8 @@ 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()?; +impl InitExprStruct { + fn parse_with_path(path: Path, input: syn::parse::ParseStream<'_>) -> syn::Result { let content; let brace_token = braced!(content in input); let mut fields = Punctuated::new(); @@ -408,6 +562,51 @@ impl Parse for Initializer { .peek(Token![..]) .then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?))) .transpose()?; + Ok(Self { + path, + brace_token, + fields, + rest, + }) + } +} + +impl InitExprTuple { + fn parse_with_path(path: Path, input: syn::parse::ParseStream<'_>) -> syn::Result { + let content; + let paren_token = parenthesized!(content in input); + let mut fields = Punctuated::new(); + while !content.is_empty() { + fields.push_value(InitTupleField { + attrs: content.call(Attribute::parse_outer)?, + left_arrow_token: content.parse()?, + value: content.parse()?, + }); + if content.is_empty() { + break; + } + fields.push_punct(content.parse()?); + } + Ok(InitExprTuple { + path, + paren_token, + 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 kind = if input.peek(token::Brace) { + InitExprKind::Struct(InitExprStruct::parse_with_path(path, input)?) + } else if input.peek(token::Paren) { + InitExprKind::Tuple(InitExprTuple::parse_with_path(path, input)?) + } else { + return Err(input.error("expected curly braces or parentheses")); + }; let error = input .peek(Token![?]) .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) @@ -426,10 +625,7 @@ impl Parse for Initializer { Ok(Self { attrs, this, - path, - brace_token, - fields, - rest, + kind, error, }) } @@ -499,3 +695,137 @@ impl Parse for InitializerKind { } } } + +impl ToTokens for Initializer { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + this, + kind, + error, + } = self; + tokens.append_all(attrs); + this.to_tokens(tokens); + kind.to_tokens(tokens); + if let Some((question, ty)) = error { + question.to_tokens(tokens); + ty.to_tokens(tokens); + } + } +} + +impl ToTokens for InitExprKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Struct(init) => init.to_tokens(tokens), + Self::Tuple(init) => init.to_tokens(tokens), + } + } +} + +impl ToTokens for InitExprStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + path, + brace_token, + fields, + rest, + } = self; + path.to_tokens(tokens); + brace_token.surround(tokens, |tokens| { + fields.to_tokens(tokens); + if let Some((dotdot, expr)) = rest { + dotdot.to_tokens(tokens); + expr.to_tokens(tokens); + } + }); + } +} + +impl ToTokens for InitExprTuple { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + path, + paren_token, + fields, + } = self; + path.to_tokens(tokens); + paren_token.surround(tokens, |tokens| fields.to_tokens(tokens)); + } +} + +impl ToTokens for InitTupleField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + left_arrow_token, + value, + } = self; + tokens.append_all(attrs); + left_arrow_token.to_tokens(tokens); + value.to_tokens(tokens); + } +} + +impl ToTokens for InitializerAttribute { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::DefaultError(DefaultErrorAttribute { ty }) => { + quote!(#[default_error(#ty)]).to_tokens(tokens); + } + } + } +} + +impl ToTokens for This { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + _and_token, + ident, + _in_token, + } = self; + _and_token.to_tokens(tokens); + ident.to_tokens(tokens); + _in_token.to_tokens(tokens); + } +} + +impl ToTokens for InitializerField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { attrs, kind } = self; + tokens.append_all(attrs); + kind.to_tokens(tokens); + } +} + +impl ToTokens for InitializerKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Value { member, value } => { + member.to_tokens(tokens); + if let Some((colon, expr)) = value { + colon.to_tokens(tokens); + expr.to_tokens(tokens); + } + } + Self::Init { + member, + _left_arrow_token, + value, + } => { + member.to_tokens(tokens); + _left_arrow_token.to_tokens(tokens); + value.to_tokens(tokens); + } + Self::Code { + _underscore_token, + _colon_token, + block, + } => { + _underscore_token.to_tokens(tokens); + _colon_token.to_tokens(tokens); + block.to_tokens(tokens); + } + } + } +} diff --git a/internal/src/lib.rs b/internal/src/lib.rs index 4d8ff864..c488019d 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -49,12 +49,17 @@ pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { #[proc_macro] pub fn init(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| init::expand(input, Some("::core::convert::Infallible"), false, dcx)) - .into() + DiagCtxt::with(|dcx| { + init::expand_with_cfg(input, Some("::core::convert::Infallible"), false, dcx) + }) + .into() } #[proc_macro] pub fn pin_init(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| init::expand(input, Some("::core::convert::Infallible"), true, dcx)).into() + DiagCtxt::with(|dcx| { + init::expand_with_cfg(input, Some("::core::convert::Infallible"), true, dcx) + }) + .into() } diff --git a/src/lib.rs b/src/lib.rs index fc34cfd7..41105903 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -648,6 +648,20 @@ macro_rules! stack_try_pin_init { /// # Box::pin_init(demo()).unwrap(); /// ``` /// +/// A tuple struct whose fields are all set to a value can also be written like a call to its +/// constructor: +/// +/// ```rust +/// # use pin_init::*; +/// #[pin_data] +/// struct Pair(usize, usize); +/// +/// # fn demo() -> impl PinInit { +/// let initializer = pin_init!(Pair(42, 64)); +/// # initializer } +/// # Box::pin_init(demo()).unwrap(); +/// ``` +/// /// Arbitrary Rust expressions can be used to set the value of a variable. /// /// The fields are initialized in the order that they appear in the initializer. So it is possible @@ -771,6 +785,8 @@ macro_rules! stack_try_pin_init { /// - Fields that you want to initialize in-place have to use `<-` instead of `:`. /// - Tuple struct fields are named by their index, as in `0: value` or `0 <- initializer`. They /// are not exposed by a `let` binding, since they have no name to bind. +/// - A tuple struct can also be initialized with constructor syntax, as in `Type(value, value)`. +/// Since its arguments are not named, they cannot use `<-`; write them out by index instead. /// - You can use `_: { /* run any user-code here */ },` anywhere where you can place fields in /// order to run arbitrary code. /// - In front of the initializer you can write `&this in` to have access to a [`NonNull`] diff --git a/tests/cfg_explode.rs b/tests/cfg_explode.rs new file mode 100644 index 00000000..eb778122 --- /dev/null +++ b/tests/cfg_explode.rs @@ -0,0 +1,37 @@ +#![allow(unexpected_cfgs)] + +use pin_init::*; + +// `#[pin_data]` and `[pin_]init!` resolve field cfgs by re-invoking themselves once per `cfg`'d +// field. Only one of the two generated branches is ever expanded, so this stays linear; were it +// exponential in the number of `cfg`s, this test would not finish. +macro_rules! explode { + ($($field:ident)*) => { + #[pin_data] + pub struct Tuple( + $( + #[cfg($field)] + u32, + )* + u32, + ); + + fn init_tuple() -> impl PinInit { + pin_init!(Tuple( + $( + #[cfg($field)] + 1, + )* + 0, + )) + } + }; +} + +explode!(a b c d e f g h i j k l m n o p q r s t u v w x y z); + +#[test] +fn cfg_explode() { + stack_pin_init!(let tuple = init_tuple()); + assert_eq!(tuple.as_ref().get_ref().0, 0); +} diff --git a/tests/cfgs.rs b/tests/cfgs.rs index ffa2e862..d8e363aa 100644 --- a/tests/cfgs.rs +++ b/tests/cfgs.rs @@ -41,6 +41,15 @@ impl TupleStruct { 1: 20, }) } + + pub fn new_from_constructor() -> impl PinInit { + pin_init!(Self( + #[cfg(any())] + Field, + 10, + 20, + )) + } } #[test] diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs index 59bf1265..e2610d28 100644 --- a/tests/tuple_struct.rs +++ b/tests/tuple_struct.rs @@ -40,6 +40,38 @@ fn init_without_pinning() { assert_eq!(triple.as_ref().get_ref().2, 43); } +#[test] +fn tuple_struct_constructor_syntax() { + stack_pin_init!(let pinned = pin_init!(Triple(11, 29, 31))); + stack_pin_init!(let unpinned = init!(Triple(11, 29, 31))); + + for triple in [pinned.as_ref().get_ref(), unpinned.as_ref().get_ref()] { + assert_eq!(triple.0, 11); + assert_eq!(triple.1, 29); + assert_eq!(triple.2, 31); + } +} + +#[pin_data] +struct ValueTuple(T, i32); + +#[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] +#[allow(clippy::just_underscores_and_digits)] +fn tuple_struct_constructor_does_not_shadow_numeric_identifiers() { + let _0 = 6; + stack_pin_init!(let tuple = pin_init!(ValueTuple(9u32, _0))); + + assert_eq!(tuple.as_ref().get_ref().1, 6); +} + #[pin_data] struct DualPinned(#[pin] CMutex, #[pin] CMutex, usize); 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..1edc0812 --- /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; name the fields by index instead, e.g. `Type { 0 <- initializer, 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_syntax_mixing.rs b/tests/ui/compile-fail/init/no_tuple_syntax_mixing.rs new file mode 100644 index 00000000..340fb512 --- /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..fde31e46 --- /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:33 + | +7 | let _ = pin_init!(Tuple(0, 1: 24)); + | ^ diff --git a/tests/ui/expand/tuple_struct.expanded.rs b/tests/ui/expand/tuple_struct.expanded.rs index 26a29b1d..81287de7 100644 --- a/tests/ui/expand/tuple_struct.expanded.rs +++ b/tests/ui/expand/tuple_struct.expanded.rs @@ -198,4 +198,62 @@ fn main() { }; 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)] + let _ = || unsafe { + let _ = &(*slot).0; + let _ = &(*slot).1; + let _ = &(*slot).2; + ::core::ptr::write( + slot, + Foo { + 0: loop {}, + 1: loop {}, + 2: loop {}, + }, + ) + }; + 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 f193bc18..27d8c7f7 100644 --- a/tests/ui/expand/tuple_struct.rs +++ b/tests/ui/expand/tuple_struct.rs @@ -11,4 +11,7 @@ fn main() { 1: PhantomPinned, 2 <- 10, }); + + let mut second = [4u8, 5, 6]; + let _ = init!(Foo(&mut second, PhantomPinned, 20)); }