From 6e526a4722c2aaa0619752b0b7f0c438fa295f80 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 28 Aug 2026 18:06:22 +0300 Subject: [PATCH 1/4] fix build with latest nightly Binding a value of an uninhabited type now makes everything following it unreachable. In `stack_pin_init!` the `Infallible` annotation is such a binding, so the `match x {}` after it is reported as unreachable and `-Dwarnings` turns that into an error. Annotate the `Result` instead of the error value, which requires the initializer to be infallible just the same but has nothing following it. `Infallible` is also printed as `!` in diagnostics now, so bless the two tests that show it. Signed-off-by: Mohamad Alsadhan --- src/lib.rs | 12 +++++++----- tests/ui/compile-fail/init/no_error_coercion.stderr | 4 ++-- tests/ui/compile-fail/pin_data/missing_pin.stderr | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7600cdbb..37bb8e5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -490,12 +490,14 @@ macro_rules! stack_pin_init { (let $var:ident $(: $t:ty)? = $val:expr) => { let val = $val; let mut $var = ::core::pin::pin!($crate::__internal::StackInit$(::<$t>)?::uninit()); - let mut $var = match $crate::__internal::StackInit::init($var, val) { + // The `Infallible` error type is what requires the initializer to be infallible. It has + // to be annotated here rather than in the `Err` arm below, because binding a value of an + // uninhabited type makes everything following it unreachable. + let res: ::core::result::Result<_, ::core::convert::Infallible> = + $crate::__internal::StackInit::init($var, val); + let mut $var = match res { Ok(res) => res, - Err(x) => { - let x: ::core::convert::Infallible = x; - match x {} - } + Err(x) => match x {}, }; }; } diff --git a/tests/ui/compile-fail/init/no_error_coercion.stderr b/tests/ui/compile-fail/init/no_error_coercion.stderr index 974c3c15..ddc13df7 100644 --- a/tests/ui/compile-fail/init/no_error_coercion.stderr +++ b/tests/ui/compile-fail/init/no_error_coercion.stderr @@ -7,8 +7,8 @@ error[E0277]: `?` couldn't convert the error to `std::alloc::AllocError` 19 | | }? AllocError) | | ^ | | | - | |______________________the trait `From` is not implemented for `std::alloc::AllocError` - | this can't be annotated with `?` because it has type `Result<_, Infallible>` + | |______________________the trait `From` is not implemented for `std::alloc::AllocError` + | this can't be annotated with `?` because it has type `Result<_, !>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = note: this error originates in the macro `init` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/compile-fail/pin_data/missing_pin.stderr b/tests/ui/compile-fail/pin_data/missing_pin.stderr index f480b637..fa39129f 100644 --- a/tests/ui/compile-fail/pin_data/missing_pin.stderr +++ b/tests/ui/compile-fail/pin_data/missing_pin.stderr @@ -8,7 +8,7 @@ error[E0277]: the trait bound `impl PinInit: Init` is not satis | |__________- required by a bound introduced by this call | help: the trait `Init` is not implemented for `impl PinInit` - but trait `Init, Infallible>` is implemented for it + but trait `Init, !>` is implemented for it --> src/lib.rs | | unsafe impl Init for T {} From 2323e02526bd0b193322771b3a599a0475942513 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`, ...; put that mapping in one place, since `[pin_]init!` has to call the accessors it names. 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 --- CHANGELOG.md | 2 + internal/src/lib.rs | 1 + internal/src/member.rs | 19 +++ internal/src/pin_data.rs | 130 +++++++++++----- 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 + 12 files changed, 356 insertions(+), 35 deletions(-) create mode 100644 internal/src/member.rs 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..78caf8a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `#[pin_data]` now supports tuple structs. Their fields have no names, so the generated + projection is a tuple struct too and its fields are accessed by index. - `[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/lib.rs b/internal/src/lib.rs index 60d5093f..a3071027 100644 --- a/internal/src/lib.rs +++ b/internal/src/lib.rs @@ -16,6 +16,7 @@ use crate::diagnostics::DiagCtxt; mod diagnostics; mod init; +mod member; mod pin_data; mod pinned_drop; mod zeroable; diff --git a/internal/src/member.rs b/internal/src/member.rs new file mode 100644 index 00000000..91307b56 --- /dev/null +++ b/internal/src/member.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro2::Ident; +use quote::format_ident; +use syn::{Index, Member}; + +/// Returns the identifier used to name the items that `#[pin_data]` generates for `member`. +/// +/// Tuple fields have no name of their own, so they are named `_0`, `_1`, ... instead. This is +/// used for the pin-data accessors and the fields of the `__Unpin` struct. +/// +/// `#[pin_data]` defines the accessors and the `[pin_]init!` macros call them, so both have to +/// agree on this mapping. +pub(crate) fn member_ident(member: &Member) -> Ident { + match member { + Member::Named(ident) => ident.clone(), + Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), + } +} diff --git a/internal/src/pin_data.rs b/internal/src/pin_data.rs index ff194d27..e795da70 100644 --- a/internal/src/pin_data.rs +++ b/internal/src/pin_data.rs @@ -7,10 +7,14 @@ 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::diagnostics::{DiagCtxt, ErrorGuaranteed}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + member::member_ident, +}; pub(crate) mod kw { syn::custom_keyword!(PinnedDrop); @@ -46,9 +50,23 @@ impl ToTokens for Args { struct FieldInfo<'a> { field: &'a Field, + member: Member, pinned: bool, } +impl FieldInfo<'_> { + fn ident(&self) -> Ident { + member_ident(&self.member) + } + + fn display_name(&self) -> String { + match &self.member { + Member::Named(ident) => format!("`{ident}`"), + Member::Unnamed(Index { index, .. }) => format!("index `{index}`"), + } + } +} + pub(crate) fn pin_data( args: Args, input: Item, @@ -136,10 +154,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(); @@ -151,23 +171,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.display_name(), ), ); } @@ -175,8 +202,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); @@ -238,7 +270,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.ident(); let ty = &f.field.ty; quote!( #ident: #ty @@ -320,6 +352,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(); @@ -332,28 +365,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.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, ), ) } @@ -362,24 +399,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.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.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 @@ -397,10 +462,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 } } } @@ -421,11 +483,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.ident(); + let member = &f.member; let pin_marker = if f.pinned { quote!(Pinned) } else { @@ -450,7 +510,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/src/lib.rs b/src/lib.rs index 37bb8e5e..7528fb24 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 41fb0ae55df563b5a49c303eea7abb2a5453b9bd 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. `cfg` needs different treatment from named fields. Non-derive proc macros are invoked before cfg is resolved, so 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. 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 because only one of the two branches is ever expanded. Named fields do not renumber, so they keep using the existing attribute-based handling. Link: https://github.com/Rust-for-Linux/pin-init/pull/165 Suggested-by: Gary Guo Signed-off-by: Mohamad Alsadhan --- CHANGELOG.md | 2 + internal/src/init.rs | 295 +++++++++++++++--- internal/src/lib.rs | 11 +- src/lib.rs | 32 +- tests/cfgs.rs | 63 +++- tests/tuple_struct.rs | 170 ++++++++++ .../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 +- 16 files changed, 687 insertions(+), 46 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 78caf8a8..a1c44305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `#[pin_data]` now supports tuple structs. Their fields have no names, so the generated projection is a tuple struct too and its fields are accessed by index. +- `[pin_]init!` now supports tuple structs, whose fields are named by their index, as in + `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..f216b7da 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -1,17 +1,21 @@ // 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, 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}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + member::member_ident, +}; pub(crate) struct Initializer { attrs: Vec, @@ -36,11 +40,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 +56,16 @@ enum InitializerKind { } impl InitializerKind { - fn ident(&self) -> Option<&Ident> { + fn member(&self) -> Option<&Member> { + match self { + Self::Value { member, .. } | Self::Init { member, .. } => Some(member), + Self::Code { .. } => None, + } + } + + fn member_mut(&mut self) -> Option<&mut Member> { match self { - Self::Value { ident, .. } | Self::Init { ident, .. } => Some(ident), + Self::Value { member, .. } | Self::Init { member, .. } => Some(member), Self::Code { .. } => None, } } @@ -68,7 +79,100 @@ struct DefaultErrorAttribute { ty: Box, } -pub(crate) fn expand( +pub(crate) fn expand_with_cfg( + mut initializer: Initializer, + default_error: Option<&'static str>, + pinned: bool, + dcx: &mut DiagCtxt, +) -> Result { + // 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. Named fields do not renumber, so they keep using Rust's normal cfg + // handling and are deliberately left alone here. + // + // We need to perform this after parsing so we can reliably detect field cfgs. + if let Some((field_idx, removed_index, cfg)) = take_first_tuple_cfg(&mut initializer.fields) { + let true_initializer = initializer.to_token_stream(); + remove_tuple_field(&mut initializer.fields, field_idx, removed_index); + 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 } + } + }); + } + + expand(initializer, default_error, pinned, dcx) +} + +/// Takes the `cfg` attributes off the first tuple field that has any. +/// +/// Returns the position of that field, its tuple index and the conditions of the removed `cfg`s. +fn take_first_tuple_cfg( + fields: &mut Punctuated, +) -> Option<(usize, u32, Vec)> { + fields.iter_mut().enumerate().find_map(|(index, field)| { + let Some(Member::Unnamed(unnamed)) = field.kind.member() else { + return None; + }; + let unnamed = unnamed.index; + let cfg: Vec<_> = field + .attrs + .iter() + .filter(|attr| attr.path().is_ident("cfg")) + .map(|attr| { + attr.parse_args::() + .expect("parse as token stream cannot fail") + }) + .collect(); + + if cfg.is_empty() { + return None; + } + + field.attrs.retain(|attr| !attr.path().is_ident("cfg")); + Some((index, unnamed, cfg)) + }) +} + +/// Removes the field at `field_idx`, which is the tuple field `removed_index`. +fn remove_tuple_field( + fields: &mut Punctuated, + field_idx: usize, + removed_index: u32, +) { + *fields = std::mem::take(fields) + .into_pairs() + .enumerate() + .filter(|&(index, _)| index != field_idx) + .map(|(_, pair)| pair) + .collect(); + // Removing a tuple field shifts every field after it down by one. + for field in fields.iter_mut() { + if let Some(Member::Unnamed(index)) = field.kind.member_mut() { + if index.index > removed_index { + index.index -= 1; + } + } + } +} + +fn expand( Initializer { attrs, this, @@ -229,9 +333,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! { @@ -244,40 +348,42 @@ fn init_fields( } }; + let accessor = 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) }) + // - `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.#accessor(#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 ) }) } }; // `mixed_site` ensures that the guard is not accessible to the user-controlled code. - let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); + let guard = format_ident!("__{accessor}_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 +400,23 @@ fn init_fields( InitializerKind::Code { .. } => unreachable!(), }; + // A tuple field has no name that could be bound here, and binding it as `_0` would + // shadow a user variable of that name. + 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 +441,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 +491,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) { @@ -456,22 +572,38 @@ 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()?; + Member::Unnamed(Index { + index: lit.base10_parse()?, + 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 }) + // Unlike a named field, a tuple field has no shorthand: `0` is not a variable. + if matches!(member, Member::Unnamed(_)) { + Err(lh.error()) + } else { + Ok(Self::Value { + member, + value: None, + }) + } } else { Err(lh.error()) } @@ -480,3 +612,94 @@ impl Parse for InitializerKind { } } } + +impl ToTokens for Initializer { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + this, + path, + brace_token, + fields, + rest, + error, + } = self; + tokens.append_all(attrs); + this.to_tokens(tokens); + 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); + } + }); + if let Some((question, ty)) = error { + question.to_tokens(tokens); + ty.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 a3071027..8934ae05 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 7528fb24..e12271b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -600,7 +600,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. @@ -634,6 +634,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 @@ -752,9 +774,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`] @@ -793,7 +817,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..fe7e04ff 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,64 @@ pub struct Struct2 { #[cfg(any())] non_exist: NonExistentType, } + +#[allow(dead_code)] +struct HiddenField; + +#[pin_data] +pub struct TupleStruct(#[cfg(any())] HiddenField, u32, u32); + +impl TupleStruct { + pub fn new() -> impl PinInit { + pin_init!(Self { + #[cfg(any())] + 0: HiddenField, + 1: 10, + 2: 20, + }) + } +} + +#[test] +fn tuple_fields_are_renumbered_around_cfgd_out_fields() { + stack_pin_init!(let value = TupleStruct::new()); + // Fields `1` and `2` became `0` and `1`, because field `0` is `cfg`'d out. + assert_eq!(value.as_ref().get_ref().0, 10); + assert_eq!(value.as_ref().get_ref().1, 20); +} + +#[pin_data] +pub struct FeatureTupleStruct( + #[cfg(not(feature = "std"))] + #[pin] + core::marker::PhantomPinned, + u32, +); + +impl FeatureTupleStruct { + pub fn new() -> impl PinInit { + pin_init!(Self { + #[cfg(not(feature = "std"))] + 0: core::marker::PhantomPinned, + 1: 5, + }) + } +} + +#[test] +fn tuple_fields_follow_feature_cfgs() { + #[cfg(not(feature = "std"))] + fn assert_pinned(_: core::pin::Pin<&mut T>) {} + + stack_pin_init!(let value = FeatureTupleStruct::new()); + let projected = value.as_mut().project(); + #[cfg(not(feature = "std"))] + { + assert_pinned(projected.0); + assert_eq!(*projected.1, 5); + } + #[cfg(feature = "std")] + { + assert_eq!(*projected.0, 5); + } +} diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs new file mode 100644 index 00000000..62aefaf5 --- /dev/null +++ b/tests/tuple_struct.rs @@ -0,0 +1,170 @@ +#![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::*; + +fn assert_pinned_mutex(_: &Pin<&mut CMutex>) {} + +fn assert_unpin() {} + +#[pin_data] +struct TupleStruct(#[pin] CMutex, i32); + +#[test] +fn tuple_struct_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); + +#[test] +fn tuple_struct_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 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); +} + +#[pin_data] +struct GenericTuple<'a, T, const N: usize>(#[pin] CMutex<(&'a T, [u8; N])>, usize); + +#[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); +} + +#[pin_data] +struct TupleConst(#[pin] CMutex<[T; N]>, usize); + +#[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] +#[allow(dead_code)] +struct UnpinnedMutexTuple(CMutex, usize); + +#[test] +fn tuple_struct_unpin_ignores_unpinned_non_unpin_field() { + assert_unpin::>(); +} + +#[pin_data(PinnedDrop)] +struct DropTuple(#[pin] CMutex, usize); + +static PINNED_DROP_TUPLE_DROPS: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +#[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 + ); +} + +static FALLIBLE_TUPLE_DROPS: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(0); + +struct DropCounter; + +impl Drop for DropCounter { + fn drop(&mut self) { + FALLIBLE_TUPLE_DROPS.fetch_add(1, core::sync::atomic::Ordering::SeqCst); + } +} + +#[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 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(InitError))); + assert_eq!( + FALLIBLE_TUPLE_DROPS.load(core::sync::atomic::Ordering::SeqCst), + 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 3ec0b2f7cdf0e9e4ecd455dd18255227ba69d928 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 28 Aug 2026 14:32:11 +0300 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. Signed-off-by: Mohamad Alsadhan --- CHANGELOG.md | 5 +- internal/src/init.rs | 397 ++++++++++++++---- src/lib.rs | 16 + tests/cfg_explode.rs | 37 ++ tests/cfgs.rs | 19 +- 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 + 12 files changed, 504 insertions(+), 89 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 a1c44305..aed4103c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `#[pin_data]` now supports tuple structs. Their fields have no names, so the generated projection is a tuple struct too and its fields are accessed by index. -- `[pin_]init!` now supports tuple structs, whose fields are named by their index, as in - `init!(Foo { 0: value, 1 <- initializer })`. +- `[pin_]init!` now supports tuple structs, either by naming the fields by their index, as in + `init!(Foo { 0: value, 1 <- initializer })`, or with constructor syntax, as in + `init!(Foo(value, value))`. - `[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 f216b7da..bf187db5 100644 --- a/internal/src/init.rs +++ b/internal/src/init.rs @@ -3,10 +3,10 @@ use proc_macro2::{Span, TokenStream}; 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, Index, LitInt, Member, Path, Token, Type, @@ -20,13 +20,126 @@ use crate::{ pub(crate) struct Initializer { attrs: Vec, this: Option, + body: InitializerBody, + error: Option<(Token![?], Type)>, +} + +enum InitializerBody { + Struct(InitStruct), + Tuple(InitTuple), +} + +struct InitStruct { path: Path, brace_token: token::Brace, fields: Punctuated, rest: Option<(Token![..], Expr)>, +} + +struct InitTuple { + path: Path, + paren_token: token::Paren, + fields: Punctuated, +} + +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, +} + +/// An [`Initializer`] with the constructor syntax rewritten into indexed fields. +struct NormalizedInitializer { + attrs: Vec, + this: Option, + path: Path, + delim_close_span: Span, + fields: Punctuated, + rest: Option<(Token![..], Expr)>, error: Option<(Token![?], Type)>, } +impl Initializer { + fn normalize(self) -> NormalizedInitializer { + let Self { + attrs, + this, + body, + error, + } = self; + let (path, delim_close_span, fields, rest) = match body { + InitializerBody::Struct(InitStruct { + path, + brace_token, + fields, + rest, + }) => (path, brace_token.span.close(), fields, rest), + InitializerBody::Tuple(InitTuple { + path, + paren_token, + fields, + }) => ( + path, + paren_token.span.close(), + index_tuple_fields(fields), + None, + ), + }; + NormalizedInitializer { + attrs, + this, + path, + delim_close_span, + fields, + rest, + error, + } + } +} + +impl InitTuple { + 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 + } +} + +/// Rewrites constructor arguments into the indexed fields they are shorthand for. +fn index_tuple_fields( + fields: Punctuated, +) -> Punctuated { + 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 { + index: index as u32, + span, + }), + value: Some((Token![:](span), field.value)), + }, + }; + Pair::new(field, comma) + }) + .collect() +} + struct This { _and_token: Token![&], ident: Ident, @@ -91,9 +204,11 @@ pub(crate) fn expand_with_cfg( // handling and are deliberately left alone here. // // We need to perform this after parsing so we can reliably detect field cfgs. - if let Some((field_idx, removed_index, cfg)) = take_first_tuple_cfg(&mut initializer.fields) { + if let Some((field_idx, removed_index, cfg)) = initializer.body.take_first_tuple_cfg() { let true_initializer = initializer.to_token_stream(); - remove_tuple_field(&mut initializer.fields, field_idx, removed_index); + initializer + .body + .remove_tuple_field(field_idx, removed_index); let false_initializer = &initializer; let macro_name = if pinned { @@ -117,71 +232,102 @@ pub(crate) fn expand_with_cfg( }); } - expand(initializer, default_error, pinned, dcx) + if let InitializerBody::Tuple(init) = &initializer.body { + init.validate(dcx)?; + } + expand(initializer.normalize(), default_error, pinned, dcx) } -/// Takes the `cfg` attributes off the first tuple field that has any. -/// -/// Returns the position of that field, its tuple index and the conditions of the removed `cfg`s. -fn take_first_tuple_cfg( - fields: &mut Punctuated, -) -> Option<(usize, u32, Vec)> { - fields.iter_mut().enumerate().find_map(|(index, field)| { - let Some(Member::Unnamed(unnamed)) = field.kind.member() else { - return None; - }; - let unnamed = unnamed.index; - let cfg: Vec<_> = field - .attrs - .iter() - .filter(|attr| attr.path().is_ident("cfg")) - .map(|attr| { - attr.parse_args::() - .expect("parse as token stream cannot fail") - }) - .collect(); +impl InitializerBody { + /// Takes the `cfg` attributes off the first tuple field that has any. + /// + /// Returns the position of that field, its tuple index and the conditions of the removed + /// `cfg`s. + fn take_first_tuple_cfg(&mut self) -> Option<(usize, u32, Vec)> { + match self { + // Only numeric members are tuple fields; named fields keep using Rust's normal cfg + // handling. + Self::Struct(init) => init + .fields + .iter_mut() + .enumerate() + .find_map(|(index, field)| { + let Some(Member::Unnamed(unnamed)) = field.kind.member() else { + return None; + }; + let unnamed = unnamed.index; + take_cfg(&mut field.attrs).map(|cfg| (index, unnamed, cfg)) + }), + // Constructor arguments are tuple fields by construction and their index is their + // position. + Self::Tuple(init) => init + .fields + .iter_mut() + .enumerate() + .find_map(|(index, field)| { + take_cfg(&mut field.attrs).map(|cfg| (index, index as u32, cfg)) + }), + } + } - if cfg.is_empty() { - return None; + /// Removes the field at `field_idx`, which is the tuple field `removed_index`. + fn remove_tuple_field(&mut self, field_idx: usize, removed_index: u32) { + match self { + Self::Struct(init) => { + remove_field(&mut init.fields, field_idx); + // Removing a tuple field shifts every field after it down by one. + for field in init.fields.iter_mut() { + if let Some(Member::Unnamed(index)) = field.kind.member_mut() { + if index.index > removed_index { + index.index -= 1; + } + } + } + } + // Constructor arguments are renumbered implicitly, by their position. + Self::Tuple(init) => remove_field(&mut init.fields, field_idx), } + } +} - field.attrs.retain(|attr| !attr.path().is_ident("cfg")); - Some((index, unnamed, cfg)) - }) +/// Takes the `cfg` attributes off `attrs` and returns their conditions. +fn take_cfg(attrs: &mut Vec) -> Option> { + let cfg: Vec<_> = attrs + .iter() + .filter(|attr| attr.path().is_ident("cfg")) + .map(|attr| { + attr.parse_args::() + .expect("parse as token stream cannot fail") + }) + .collect(); + + if cfg.is_empty() { + return None; + } + + attrs.retain(|attr| !attr.path().is_ident("cfg")); + Some(cfg) } -/// Removes the field at `field_idx`, which is the tuple field `removed_index`. -fn remove_tuple_field( - fields: &mut Punctuated, - field_idx: usize, - removed_index: u32, -) { +fn remove_field(fields: &mut Punctuated, field_idx: usize) { *fields = std::mem::take(fields) .into_pairs() .enumerate() .filter(|&(index, _)| index != field_idx) .map(|(_, pair)| pair) .collect(); - // Removing a tuple field shifts every field after it down by one. - for field in fields.iter_mut() { - if let Some(Member::Unnamed(index)) = field.kind.member_mut() { - if index.index > removed_index { - index.index -= 1; - } - } - } } fn expand( - Initializer { + NormalizedInitializer { attrs, this, path, - brace_token, + delim_close_span, fields, rest, error, - }: Initializer, + }: NormalizedInitializer, default_error: Option<&'static str>, pinned: bool, dcx: &mut DiagCtxt, @@ -200,7 +346,7 @@ 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(delim_close_span, "expected `? ` after initializer"); parse_quote!(::core::convert::Infallible) } }, @@ -479,37 +625,80 @@ 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 { +fn parse_brace_initializer( + path: Path, + input: syn::parse::ParseStream<'_>, +) -> syn::Result { + 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(LitInt) || 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(InitStruct { + path, + brace_token, + fields, + rest, + }) +} + +fn parse_paren_initializer( + 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(InitTuple { + 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 body = if input.peek(token::Brace) { + InitializerBody::Struct(parse_brace_initializer(path, input)?) + } else if input.peek(token::Paren) { + InitializerBody::Tuple(parse_paren_initializer(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()?))) @@ -528,10 +717,7 @@ impl Parse for Initializer { Ok(Self { attrs, this, - path, - brace_token, - fields, - rest, + body, error, }) } @@ -618,14 +804,36 @@ impl ToTokens for Initializer { let Self { attrs, this, + body, + error, + } = self; + tokens.append_all(attrs); + this.to_tokens(tokens); + body.to_tokens(tokens); + if let Some((question, ty)) = error { + question.to_tokens(tokens); + ty.to_tokens(tokens); + } + } +} + +impl ToTokens for InitializerBody { + 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 InitStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { path, brace_token, fields, rest, - error, } = self; - tokens.append_all(attrs); - this.to_tokens(tokens); path.to_tokens(tokens); brace_token.surround(tokens, |tokens| { fields.to_tokens(tokens); @@ -634,10 +842,31 @@ impl ToTokens for Initializer { expr.to_tokens(tokens); } }); - if let Some((question, ty)) = error { - question.to_tokens(tokens); - ty.to_tokens(tokens); - } + } +} + +impl ToTokens for InitTuple { + 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); } } diff --git a/src/lib.rs b/src/lib.rs index e12271b7..b28c62fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -656,6 +656,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 @@ -779,6 +793,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 fe7e04ff..07d2bf9a 100644 --- a/tests/cfgs.rs +++ b/tests/cfgs.rs @@ -43,14 +43,27 @@ impl TupleStruct { 2: 20, }) } + + pub fn new_from_constructor() -> impl PinInit { + pin_init!(Self( + #[cfg(any())] + HiddenField, + 10, + 20, + )) + } } #[test] fn tuple_fields_are_renumbered_around_cfgd_out_fields() { - stack_pin_init!(let value = TupleStruct::new()); // Fields `1` and `2` became `0` and `1`, because field `0` is `cfg`'d out. - assert_eq!(value.as_ref().get_ref().0, 10); - assert_eq!(value.as_ref().get_ref().1, 20); + stack_pin_init!(let indexed = TupleStruct::new()); + assert_eq!(indexed.as_ref().get_ref().0, 10); + assert_eq!(indexed.as_ref().get_ref().1, 20); + + stack_pin_init!(let constructed = TupleStruct::new_from_constructor()); + assert_eq!(constructed.as_ref().get_ref().0, 10); + assert_eq!(constructed.as_ref().get_ref().1, 20); } #[pin_data] diff --git a/tests/tuple_struct.rs b/tests/tuple_struct.rs index 62aefaf5..4a5ec4d1 100644 --- a/tests/tuple_struct.rs +++ b/tests/tuple_struct.rs @@ -37,6 +37,38 @@ fn tuple_struct_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)); }