mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
rust: pin-init: 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 <mo@sdhn.cc> [ Moved utility code to util.rs as extension trait - Gary ] Link: https://patch.msgid.link/20260904-tuple-struct-v1-2-72c50bd037fd@garyguo.net Signed-off-by: Gary Guo <gary@garyguo.net>
This commit is contained in:
parent
c2bc9bd605
commit
1ee699c451
|
|
@ -7,7 +7,8 @@
|
|||
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 @@ fn to_tokens(&self, tokens: &mut TokenStream) {
|
|||
|
||||
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<FieldInfo<'_>> = 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 @@ impl #impl_generics #ident #ty_generics
|
|||
) -> #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) }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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<TokenStream>;
|
||||
|
|
@ -25,3 +26,29 @@ fn extract_cfg_attrs(&mut self) -> Vec<TokenStream> {
|
|||
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}`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,6 +304,9 @@
|
|||
/// 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 @@
|
|||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// 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<Vec<Command>>, Box<[u8; 1024 * 1024]>);
|
||||
///
|
||||
/// fn queue(data: Pin<&mut DriverData>) -> Pin<&mut CMutex<Vec<Command>>> {
|
||||
/// data.project().0
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # #![feature(allocator_api)]
|
||||
/// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user