// SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ parse::{End, Nothing, Parse}, parse_quote, parse_quote_spanned, spanned::Spanned, visit_mut::VisitMut, Field, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, }; use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; pub(crate) mod kw { syn::custom_keyword!(PinnedDrop); } pub(crate) enum Args { Nothing(Nothing), #[allow(dead_code)] PinnedDrop(kw::PinnedDrop), } impl Parse for Args { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let lh = input.lookahead1(); if lh.peek(End) { input.parse().map(Self::Nothing) } else if lh.peek(kw::PinnedDrop) { input.parse().map(Self::PinnedDrop) } else { Err(lh.error()) } } } pub(crate) fn pin_data( args: Args, input: Item, dcx: &mut DiagCtxt, ) -> Result { let mut struct_ = match input { Item::Struct(struct_) => struct_, Item::Enum(enum_) => { return Err(dcx.error( enum_.enum_token, "`#[pin_data]` only supports structs for now", )); } Item::Union(union) => { return Err(dcx.error( union.union_token, "`#[pin_data]` only supports structs for now", )); } rest => { return Err(dcx.error( rest, "`#[pin_data]` can only be applied to struct, enum and union definitions", )); } }; // The generics might contain the `Self` type. Since this macro will define a new type with the // same generics and bounds, this poses a problem: `Self` will refer to the new type as opposed // to this struct definition. Therefore we have to replace `Self` with the concrete name. let mut replacer = { let name = &struct_.ident; let (_, ty_generics, _) = struct_.generics.split_for_impl(); SelfReplacer(parse_quote!(#name #ty_generics)) }; replacer.visit_generics_mut(&mut struct_.generics); replacer.visit_fields_mut(&mut struct_.fields); let fields: Vec<(bool, &Field)> = struct_ .fields .iter_mut() .map(|field| { let len = field.attrs.len(); field.attrs.retain(|a| !a.path().is_ident("pin")); (len != field.attrs.len(), &*field) }) .collect(); for (pinned, field) in &fields { if !pinned && is_phantom_pinned(&field.ty) { dcx.error( field, format!( "The field `{}` of type `PhantomPinned` only has an effect \ if it has the `#[pin]` attribute", field.ident.as_ref().unwrap(), ), ); } } let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args); let projections = generate_projections(&struct_.vis, &struct_.ident, &struct_.generics, &fields); let the_pin_data = generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields); Ok(quote! { #struct_ #projections // We put the rest into this const item, because it then will not be accessible to anything // outside. const _: () = { #the_pin_data #unpin_impl #drop_impl }; }) } fn is_phantom_pinned(ty: &Type) -> bool { match ty { Type::Path(TypePath { qself: None, path }) => { // Cannot possibly refer to `PhantomPinned` (except alias, but that's on the user). if path.segments.len() > 3 { return false; } // If there is a `::`, then the path needs to be `::core::marker::PhantomPinned` or // `::std::marker::PhantomPinned`. if path.leading_colon.is_some() && path.segments.len() != 3 { return false; } let expected: Vec<&[&str]> = vec![&["PhantomPinned"], &["marker"], &["core", "std"]]; for (actual, expected) in path.segments.iter().rev().zip(expected) { if !actual.arguments.is_empty() || expected.iter().all(|e| actual.ident != e) { return false; } } true } _ => false, } } fn generate_unpin_impl( ident: &Ident, generics: &Generics, fields: &[(bool, &Field)], ) -> TokenStream { let (_, ty_generics, _) = generics.split_for_impl(); let mut generics_with_pin_lt = generics.clone(); generics_with_pin_lt.params.insert(0, parse_quote!('__pin)); generics_with_pin_lt.make_where_clause(); let ( impl_generics_with_pin_lt, ty_generics_with_pin_lt, Some(WhereClause { where_token, predicates, }), ) = generics_with_pin_lt.split_for_impl() else { unreachable!() }; let pinned_fields = fields.iter().filter_map(|(b, f)| b.then_some(f)); quote! { // This struct will be used for the unpin analysis. It is needed, because only structurally // pinned fields are relevant whether the struct should implement `Unpin`. #[allow(dead_code)] // The fields below are never used. struct __Unpin #generics_with_pin_lt #where_token #predicates { __phantom_pin: ::core::marker::PhantomData &'__pin ()>, __phantom: ::core::marker::PhantomData< fn(#ident #ty_generics) -> #ident #ty_generics >, #(#pinned_fields),* } #[doc(hidden)] impl #impl_generics_with_pin_lt ::core::marker::Unpin for #ident #ty_generics #where_token __Unpin #ty_generics_with_pin_lt: ::core::marker::Unpin, #predicates {} } } fn generate_drop_impl(ident: &Ident, generics: &Generics, args: Args) -> TokenStream { let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let has_pinned_drop = matches!(args, Args::PinnedDrop(_)); // We need to disallow normal `Drop` implementation, the exact behavior depends on whether // `PinnedDrop` was specified in `args`. if has_pinned_drop { // When `PinnedDrop` was specified we just implement `Drop` and delegate. quote! { impl #impl_generics ::core::ops::Drop for #ident #ty_generics #whr { fn drop(&mut self) { // SAFETY: Since this is a destructor, `self` will not move after this function // terminates, since it is inaccessible. let pinned = unsafe { ::core::pin::Pin::new_unchecked(self) }; // SAFETY: Since this is a drop function, we can create this token to call the // pinned destructor of this type. let token = unsafe { ::pin_init::__internal::OnlyCallFromDrop::new() }; ::pin_init::PinnedDrop::drop(pinned, token); } } } } else { // When no `PinnedDrop` was specified, then we have to prevent implementing drop. quote! { // We prevent this by creating a trait that will be implemented for all types implementing // `Drop`. Additionally we will implement this trait for the struct leading to a conflict, // if it also implements `Drop` trait MustNotImplDrop {} #[expect(drop_bounds)] impl MustNotImplDrop for T {} impl #impl_generics MustNotImplDrop for #ident #ty_generics #whr {} // We also take care to prevent users from writing a useless `PinnedDrop` implementation. // They might implement `PinnedDrop` correctly for the struct, but forget to give // `PinnedDrop` as the parameter to `#[pin_data]`. #[expect(non_camel_case_types)] trait UselessPinnedDropImpl_you_need_to_specify_PinnedDrop {} impl UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for T {} impl #impl_generics UselessPinnedDropImpl_you_need_to_specify_PinnedDrop for #ident #ty_generics #whr {} } } } fn generate_projections( vis: &Visibility, ident: &Ident, generics: &Generics, fields: &[(bool, &Field)], ) -> TokenStream { let (impl_generics, ty_generics, _) = generics.split_for_impl(); let mut generics_with_pin_lt = generics.clone(); generics_with_pin_lt.params.insert(0, parse_quote!('__pin)); let (_, ty_generics_with_pin_lt, whr) = generics_with_pin_lt.split_for_impl(); let projection = format_ident!("{ident}Projection"); let this = format_ident!("this"); let (fields_decl, fields_proj) = collect_tuple(fields.iter().map( |( pinned, Field { vis, ident, ty, attrs, .. }, )| { let mut attrs = attrs.clone(); attrs.retain(|a| !a.path().is_ident("pin")); let mut no_doc_attrs = attrs.clone(); no_doc_attrs.retain(|a| !a.path().is_ident("doc")); let ident = ident .as_ref() .expect("only structs with named fields are supported"); if *pinned { ( quote!( #(#attrs)* #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, ), quote!( #(#no_doc_attrs)* // SAFETY: this field is structurally pinned. #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, ), ) } else { ( quote!( #(#attrs)* #vis #ident: &'__pin mut #ty, ), quote!( #(#no_doc_attrs)* #ident: &mut #this.#ident, ), ) } }, )); let structurally_pinned_fields_docs = fields .iter() .filter_map(|(pinned, field)| pinned.then_some(field)) .map(|Field { ident, .. }| format!(" - `{}`", ident.as_ref().unwrap())); let not_structurally_pinned_fields_docs = fields .iter() .filter_map(|(pinned, field)| (!pinned).then_some(field)) .map(|Field { ident, .. }| format!(" - `{}`", ident.as_ref().unwrap())); let docs = format!(" Pin-projections of [`{ident}`]"); quote! { #[doc = #docs] #[allow(dead_code)] #[doc(hidden)] #vis struct #projection #generics_with_pin_lt { #(#fields_decl)* ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, } impl #impl_generics #ident #ty_generics #whr { /// Pin-projects all fields of `Self`. /// /// These fields are structurally pinned: #(#[doc = #structurally_pinned_fields_docs])* /// /// These fields are **not** structurally pinned: #(#[doc = #not_structurally_pinned_fields_docs])* #[inline] #vis fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, ) -> #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, } } } } } fn generate_the_pin_data( vis: &Visibility, ident: &Ident, generics: &Generics, fields: &[(bool, &Field)], ) -> TokenStream { let (impl_generics, ty_generics, whr) = generics.split_for_impl(); // For every field, we create an initializing projection function according to its projection // type. If a field is structurally pinned, then it must be initialized via `PinInit`, if it is // not structurally pinned, then it can be initialized via `Init`. // // The functions are `unsafe` to prevent accidentally calling them. fn handle_field( Field { vis, ident, ty, attrs, .. }: &Field, struct_ident: &Ident, pinned: bool, ) -> TokenStream { let mut attrs = attrs.clone(); attrs.retain(|a| !a.path().is_ident("pin")); let ident = ident .as_ref() .expect("only structs with named fields are supported"); let project_ident = format_ident!("__project_{ident}"); let (init_ty, init_fn, project_ty, project_body, pin_safety) = if pinned { ( quote!(PinInit), quote!(__pinned_init), quote!(::core::pin::Pin<&'__slot mut #ty>), // SAFETY: this field is structurally pinned. quote!(unsafe { ::core::pin::Pin::new_unchecked(slot) }), quote!( /// - `slot` will not move until it is dropped, i.e. it will be pinned. ), ) } else { ( quote!(Init), quote!(__init), quote!(&'__slot mut #ty), quote!(slot), quote!(), ) }; let slot_safety = format!( " `slot` points at the field `{ident}` inside of `{struct_ident}`, which is pinned.", ); quote! { /// # Safety /// /// - `slot` is a valid pointer to uninitialized memory. /// - the caller does not touch `slot` when `Err` is returned, they are only permitted /// to deallocate. #pin_safety #(#attrs)* #vis unsafe fn #ident( self, slot: *mut #ty, init: impl ::pin_init::#init_ty<#ty, E>, ) -> ::core::result::Result<(), E> { // SAFETY: this function has the same safety requirements as the __init function // called below. unsafe { ::pin_init::#init_ty::#init_fn(init, slot) } } /// # Safety /// #[doc = #slot_safety] #(#attrs)* #vis unsafe fn #project_ident<'__slot>( self, slot: &'__slot mut #ty, ) -> #project_ty { #project_body } } } let field_accessors = fields .iter() .map(|(pinned, field)| handle_field(field, ident, *pinned)) .collect::(); quote! { // We declare this struct which will host all of the projection function for our type. It // will be invariant over all generic parameters which are inherited from the struct. #[doc(hidden)] #vis struct __ThePinData #generics #whr { __phantom: ::core::marker::PhantomData< fn(#ident #ty_generics) -> #ident #ty_generics >, } impl #impl_generics ::core::clone::Clone for __ThePinData #ty_generics #whr { fn clone(&self) -> Self { *self } } impl #impl_generics ::core::marker::Copy for __ThePinData #ty_generics #whr {} #[allow(dead_code)] // Some functions might never be used and private. #[expect(clippy::missing_safety_doc)] impl #impl_generics __ThePinData #ty_generics #whr { #field_accessors } // SAFETY: We have added the correct projection functions above to `__ThePinData` and // we also use the least restrictive generics possible. unsafe impl #impl_generics ::pin_init::__internal::HasPinData for #ident #ty_generics #whr { type PinData = __ThePinData #ty_generics; unsafe fn __pin_data() -> Self::PinData { __ThePinData { __phantom: ::core::marker::PhantomData } } } // SAFETY: TODO unsafe impl #impl_generics ::pin_init::__internal::PinData for __ThePinData #ty_generics #whr { type Datee = #ident #ty_generics; } } } struct SelfReplacer(PathSegment); impl VisitMut for SelfReplacer { fn visit_path_mut(&mut self, i: &mut syn::Path) { if i.is_ident("Self") { let span = i.span(); let seg = &self.0; *i = parse_quote_spanned!(span=> #seg); } else { syn::visit_mut::visit_path_mut(self, i); } } fn visit_path_segment_mut(&mut self, seg: &mut PathSegment) { if seg.ident == "Self" { let span = seg.span(); let this = &self.0; *seg = parse_quote_spanned!(span=> #this); } else { syn::visit_mut::visit_path_segment_mut(self, seg); } } fn visit_item_mut(&mut self, _: &mut Item) { // Do not descend into items, since items reset/change what `Self` refers to. } } // replace with `.collect()` once MSRV is above 1.79 fn collect_tuple(iter: impl Iterator) -> (Vec, Vec) { let mut res_a = vec![]; let mut res_b = vec![]; for (a, b) in iter { res_a.push(a); res_b.push(b); } (res_a, res_b) }