mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
rust: pin-init: internal: pin_data: project self-referential fields
This adds the projection for fields that are shared borrowed or that borrows other fields but is covariant. Both cases allow a shared reference to be accessed. No mutable references can be created for these cases for different reasons: * For fields that are shared borrowed, aliasing restriction prevents creation of mutable reference * For fields that borrow other fields, their proper type contains field lifetimes. These lifetimes cannot be made available in the returned `project` struct (because there is no way to represent existential lifetime in return position). For covariant types, it is possible to shorten these lifetimes to that of `&self`; but doing so requires the reference to also be covariant over the pointee type, so we cannot give out `&mut` as it is invariant over the pointee. Due to field-referencing fields being wrapped inside `Erase`, the normal accessor syntax stop working; create accessor methods for these fields instead. Acked-by: Benno Lossin <lossin@kernel.org> Link: https://patch.msgid.link/20261008-dev-selfref-v2-9-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo <gary@garyguo.net>
This commit is contained in:
parent
c7ec00c230
commit
71b0637185
|
|
@ -12,7 +12,7 @@
|
|||
visit::Visit,
|
||||
visit_mut::VisitMut,
|
||||
Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, LifetimeParam,
|
||||
Member, PathSegment, Type, TypePath,
|
||||
Member, PathSegment, Token, Type, TypePath,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -844,7 +844,8 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
|
|||
generics,
|
||||
..
|
||||
} = &info.struct_;
|
||||
let this_lt_generics: Generics = parse_quote!(<'__this>);
|
||||
let this_lt = Lifetime::new("'__this", Span::mixed_site());
|
||||
let this_lt_generics: Generics = parse_quote!(<#this_lt>);
|
||||
let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]);
|
||||
|
||||
let (impl_generics, ty_generics, whr) = generics.split_for_impl();
|
||||
|
|
@ -855,33 +856,68 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
|
|||
let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = info
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let Field { vis, ty, .. } = &field.field;
|
||||
let member = &field.member;
|
||||
.map(|f| {
|
||||
let vis = &f.field.vis;
|
||||
let ident = f.member.as_ident();
|
||||
let member = &f.member;
|
||||
// The projection of a tuple struct is a tuple struct itself, so its fields are
|
||||
// positional and must not be named.
|
||||
let name = (!info.is_tuple_struct).then(|| {
|
||||
let ident = field.member.as_ident();
|
||||
quote!(#ident:)
|
||||
});
|
||||
let name = (!info.is_tuple_struct).then(|| quote!(#ident:));
|
||||
|
||||
if field.pinned {
|
||||
// if `f.ty` contains field lifetimes, which we need to replace them with shorter
|
||||
// `'__this` lifetime as field lifetimes are not available in this context.
|
||||
let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect();
|
||||
let ty = f
|
||||
.field
|
||||
.ty
|
||||
.replace_lifetimes(&all_lifetimes, &vec![&this_lt; all_lifetimes.len()]);
|
||||
|
||||
// Fields sharedly borrowed by other fields can only be shared accessed. Fields that
|
||||
// references other field and are covariant can also only be given shared reference
|
||||
// as mutable reference is invariant.
|
||||
let mut_token: Option<Token![mut]> = if f.borrowed.is_none() && f.captures.is_empty() {
|
||||
Some(Default::default())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut accessor = quote!(&#mut_token #this.#member);
|
||||
if !f.captures.is_empty() || f.borrowed.is_some() {
|
||||
accessor = quote!(
|
||||
// SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`.
|
||||
// Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance.
|
||||
unsafe { ::core::mem::transmute::<_, &#mut_token #ty>(#accessor) }
|
||||
)
|
||||
}
|
||||
|
||||
if !f.captures.iter().all(|b| b.variance == Variance::Covariant) {
|
||||
// If the type is not covariant, it must omitted, as projection shortens the
|
||||
// lifetime to `'__this`.
|
||||
(
|
||||
quote!(
|
||||
#vis #name ::core::pin::Pin<&'__this mut #ty>,
|
||||
#vis #name ::pin_init::__internal::NotVisible<&'__this #mut_token #ty>,
|
||||
),
|
||||
quote!(
|
||||
#name ::pin_init::__internal::NotVisible::new(),
|
||||
),
|
||||
)
|
||||
} else if f.pinned {
|
||||
(
|
||||
quote!(
|
||||
#vis #name ::core::pin::Pin<&'__this #mut_token #ty>,
|
||||
),
|
||||
quote!(
|
||||
// SAFETY: this field is structurally pinned.
|
||||
#name unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) },
|
||||
#name unsafe { ::core::pin::Pin::new_unchecked(#accessor) },
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
quote!(
|
||||
#vis #name &'__this mut #ty,
|
||||
#vis #name &'__this #mut_token #ty,
|
||||
),
|
||||
quote!(
|
||||
#name &mut #this.#member,
|
||||
#name #accessor,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -931,6 +967,49 @@ fn generate_projections(info: &StructInfo) -> TokenStream {
|
|||
},
|
||||
)
|
||||
};
|
||||
|
||||
// For fields that references other fields, field access syntax stops working as they're wrapped
|
||||
// behind `Erase` because their actual lifetime is not on the struct.
|
||||
//
|
||||
// Generate an accessor method for them.
|
||||
let mut accessors = Vec::new();
|
||||
for f in info.fields.iter() {
|
||||
let ident = f.member.as_ident();
|
||||
let member = &f.member;
|
||||
|
||||
if f.captures.is_empty() {
|
||||
// They can be accessed normally, no accessor to be generated.
|
||||
continue;
|
||||
}
|
||||
|
||||
if f.captures.iter().all(|b| b.variance == Variance::Covariant) {
|
||||
let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`.");
|
||||
let vis = &f.field.vis;
|
||||
|
||||
// Use the span of type for better error message.
|
||||
let span = f.field.ty.span().resolved_at(Span::mixed_site());
|
||||
|
||||
let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect();
|
||||
let ty = f
|
||||
.field
|
||||
.ty
|
||||
.replace_lifetimes(&all_lifetimes, &vec![&this_lt; all_lifetimes.len()]);
|
||||
|
||||
accessors.push(quote_spanned!(span =>
|
||||
#[doc = #f_doc]
|
||||
#[inline]
|
||||
#[allow(clippy::mut_from_ref)] // false positive when `&&mut` is returned.
|
||||
#vis fn #ident<#this_lt>(&#this_lt self) -> &#this_lt #ty {
|
||||
// SAFETY: we have `Erased<..>` which we know is layout compatible with `f.ty`.
|
||||
// Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance.
|
||||
unsafe { ::core::mem::transmute(&self.#member) }
|
||||
}
|
||||
))
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
#[doc = #docs]
|
||||
// Allow `non_snake_case` since the same warning will be emitted on
|
||||
|
|
@ -957,6 +1036,8 @@ impl #impl_generics #ident #ty_generics
|
|||
let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) };
|
||||
#projection_init
|
||||
}
|
||||
|
||||
#(#accessors)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
//! These items must not be used outside of this crate and the pin-init-internal crate located at
|
||||
//! `../internal`.
|
||||
|
||||
#![expect(clippy::new_without_default, reason = "private API")]
|
||||
|
||||
use core::marker::PhantomPinned;
|
||||
use core::ops::Deref;
|
||||
|
||||
|
|
@ -677,3 +679,13 @@ fn deref(&self) -> &T {
|
|||
&self.1
|
||||
}
|
||||
}
|
||||
|
||||
/// An alias of `PhantomData` but with a name to aid user in case of misuse.
|
||||
pub struct NotVisible<T: ?Sized>(PhantomData<T>);
|
||||
|
||||
impl<T: ?Sized> NotVisible<T> {
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user