| // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| // Exceptions. See /LICENSE for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| #![cfg_attr(not(test), no_std)] |
| |
| extern crate alloc; |
| |
| use alloc::borrow::Cow; |
| use alloc::collections::BTreeSet; |
| use alloc::format; |
| use alloc::vec; |
| use proc_macro::TokenStream; |
| use proc_macro2::{Ident, Span}; |
| use quote::{quote, quote_spanned, ToTokens as _}; |
| use syn::parse::Parse; |
| use syn::spanned::Spanned as _; |
| use syn::Token; |
| |
| // TODO(jeanpierreda): derive constructors and assignment for copy and move. |
| |
| const FIELD_FOR_MUST_USE_CTOR: &'static str = "__must_use_ctor_to_initialize"; |
| |
| #[proc_macro_derive(CtorFrom_Default)] |
| pub fn derive_default(item: TokenStream) -> TokenStream { |
| let input = syn::parse_macro_input!(item as syn::DeriveInput); |
| |
| let struct_name = input.ident; |
| let struct_ctor_name = |
| Ident::new(&format!("_ctor_derive_{}_CtorType_Default", struct_name), Span::call_site()); |
| let fields: proc_macro2::TokenStream = match &input.data { |
| syn::Data::Struct(data) => { |
| if let syn::Fields::Unit = data.fields { |
| quote! {} |
| } else { |
| let filled_fields = data.fields.iter().enumerate().filter_map(|(i, field)| { |
| let field_i = syn::Index::from(i); |
| let field_name; |
| // This logic is here in case you derive default on the output of |
| // `#[recursively_pinned]`, but it's obviously not very flexible. For example, |
| // maybe we want to compute a non-colliding field name, and maybe there are |
| // other ordering problems. |
| match &field.ident { |
| Some(name) if name == FIELD_FOR_MUST_USE_CTOR => return None, |
| Some(name) => field_name = quote! {#name}, |
| None => field_name = quote! {#field_i}, |
| }; |
| |
| let field_type = &field.ty; |
| Some(quote_spanned! {field.span() => |
| #field_name: <#field_type as ::ctor::CtorNew<()>>::ctor_new(()) |
| }) |
| }); |
| quote! {{ #(#filled_fields),* }} |
| } |
| } |
| syn::Data::Enum(e) => { |
| return syn::Error::new(e.enum_token.span, "Enums are not supported") |
| .into_compile_error() |
| .into(); |
| } |
| syn::Data::Union(u) => { |
| return syn::Error::new(u.union_token.span, "Unions are not supported") |
| .into_compile_error() |
| .into(); |
| } |
| }; |
| |
| let expanded = quote! { |
| struct #struct_ctor_name(); |
| |
| impl ::ctor::Ctor for #struct_ctor_name { |
| type Output = #struct_name; |
| unsafe fn ctor(self, dest: ::core::pin::Pin<&mut ::core::mem::MaybeUninit<Self::Output>>) { |
| ::ctor::ctor!( |
| #struct_name #fields |
| ).ctor(dest) |
| } |
| } |
| |
| impl !::core::marker::Unpin for #struct_ctor_name {} |
| |
| impl ::ctor::CtorNew<()> for #struct_name { |
| type CtorType = #struct_ctor_name; |
| |
| fn ctor_new(_args: ()) -> #struct_ctor_name { #struct_ctor_name() } |
| } |
| }; |
| TokenStream::from(expanded) |
| } |
| |
| /// `project_pin_type!(foo::T)` is the name of the type returned by |
| /// `foo::T::project_pin()`. |
| /// |
| /// If `foo::T` is not `#[recursively_pinned]`, then this returns the name it |
| /// would have used, but is essentially useless. |
| #[proc_macro] |
| pub fn project_pin_type(name: TokenStream) -> TokenStream { |
| let mut name = syn::parse_macro_input!(name as syn::Path); |
| match name.segments.last_mut() { |
| None => { |
| return syn::Error::new(name.span(), "Path must have at least one element") |
| .into_compile_error() |
| .into(); |
| } |
| Some(last) => { |
| if let syn::PathArguments::Parenthesized(p) = &last.arguments { |
| return syn::Error::new( |
| p.span(), |
| "Parenthesized paths (e.g. fn, Fn) do not have projected equivalents.", |
| ) |
| .into_compile_error() |
| .into(); |
| } |
| last.ident = project_pin_ident(&last.ident); |
| } |
| } |
| TokenStream::from(quote! { #name }) |
| } |
| |
| fn project_pin_ident(ident: &Ident) -> Ident { |
| Ident::new(&format!("__CrubitProjectPin{}", ident), Span::call_site()) |
| } |
| |
| /// Defines the `project_pin` function, and its return value. |
| /// |
| /// If the input is a union, this returns nothing, and pin-projection is not |
| /// implemented. |
| fn project_pin_impl(input: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> { |
| let is_fieldless = match &input.data { |
| syn::Data::Struct(data) => data.fields.is_empty(), |
| syn::Data::Enum(e) => e.variants.iter().all(|variant| variant.fields.is_empty()), |
| syn::Data::Union(_) => { |
| return Ok(quote! {}); |
| } |
| }; |
| |
| let mut projected = input.clone(); |
| // TODO(jeanpierreda): check attributes for repr(packed) |
| projected.attrs.clear(); |
| projected.ident = project_pin_ident(&projected.ident); |
| |
| let lifetime = if is_fieldless { |
| quote! {} |
| } else { |
| add_lifetime(&mut projected.generics, "'proj") |
| }; |
| |
| let project_field = |field: &mut syn::Field| { |
| field.attrs.clear(); |
| let field_ty = &field.ty; |
| let pin_ty = syn::parse_quote!(::core::pin::Pin<& #lifetime mut #field_ty>); |
| field.ty = syn::Type::Path(pin_ty); |
| }; |
| // returns the braced parts of a projection pattern and return value. |
| // e.g. {foo, bar, ..}, {foo: Pin::new_unchecked(foo), bar: |
| // Pin::new_unchecked(bar)} |
| let pat_project = |fields: &mut syn::Fields| { |
| let mut pat = quote! {}; |
| let mut project = quote! {}; |
| for (i, field) in fields.iter_mut().enumerate() { |
| // TODO(jeanpierreda): check attributes for e.g. #[unpin] |
| field.attrs.clear(); |
| let lhs; |
| let rhs; |
| if let Some(ident) = &field.ident { |
| lhs = quote! {#ident}; |
| rhs = ident.clone(); |
| pat.extend(quote! {#lhs,}); |
| } else { |
| lhs = proc_macro2::Literal::usize_unsuffixed(i).into_token_stream(); |
| rhs = Ident::new(&format!("item_{i}"), Span::call_site()); |
| pat.extend(quote! {#lhs: #rhs,}); |
| } |
| project.extend(quote! {#lhs: ::core::pin::Pin::new_unchecked(#rhs),}); |
| } |
| // Also ignore the __must_use_ctor_to_initialize field, if present. |
| pat.extend(quote! {..}); |
| (quote! {{#pat}}, quote! {{#project}}) |
| }; |
| let project_body; |
| let input_ident = &input.ident; |
| let projected_ident = &projected.ident; |
| match &mut projected.data { |
| syn::Data::Struct(data) => { |
| for field in &mut data.fields { |
| project_field(field); |
| } |
| let (pat, project) = pat_project(&mut data.fields); |
| project_body = quote! { |
| let #input_ident #pat = from; |
| #projected_ident #project |
| }; |
| } |
| syn::Data::Enum(e) => { |
| let mut match_body = quote! {}; |
| for variant in &mut e.variants { |
| for field in &mut variant.fields { |
| project_field(field); |
| } |
| let (pat, project) = pat_project(&mut variant.fields); |
| let variant_ident = &variant.ident; |
| match_body.extend(quote! { |
| #input_ident::#variant_ident #pat => #projected_ident::#variant_ident #project, |
| }); |
| } |
| project_body = quote! { |
| match from { |
| #match_body |
| } |
| }; |
| } |
| syn::Data::Union(_) => { |
| unreachable!("project_pin_impl should early return when it finds a union") |
| } |
| } |
| |
| let (input_impl_generics, input_ty_generics, input_where_clause) = |
| input.generics.split_for_impl(); |
| let (_, projected_generics, _) = projected.generics.split_for_impl(); |
| |
| Ok(quote! { |
| #projected |
| |
| impl #input_impl_generics #input_ident #input_ty_generics #input_where_clause { |
| #[must_use] |
| pub fn project_pin<#lifetime>(self: ::core::pin::Pin<& #lifetime mut Self>) -> #projected_ident #projected_generics { |
| unsafe { |
| let from = ::core::pin::Pin::into_inner_unchecked(self); |
| #project_body |
| } |
| } |
| } |
| }) |
| } |
| |
| /// Adds a new lifetime to `generics`, returning the quoted lifetime name. |
| fn add_lifetime(generics: &mut syn::Generics, prefix: &str) -> proc_macro2::TokenStream { |
| let taken_lifetimes: BTreeSet<&syn::Lifetime> = |
| generics.lifetimes().map(|def| &def.lifetime).collect(); |
| let mut name = Cow::Borrowed(prefix); |
| let mut i = 1; |
| let lifetime = loop { |
| let lifetime = syn::Lifetime::new(&name, Span::call_site()); |
| if !taken_lifetimes.contains(&lifetime) { |
| break lifetime; |
| } |
| |
| i += 1; |
| name = Cow::Owned(format!("{prefix}_{i}")); |
| }; |
| let quoted_lifetime = quote! {#lifetime}; |
| generics.params.push(syn::GenericParam::Lifetime(syn::LifetimeDef::new(lifetime))); |
| quoted_lifetime |
| } |
| |
| #[derive(Default)] |
| struct RecursivelyPinnedArgs { |
| is_pinned_drop: bool, |
| } |
| |
| impl Parse for RecursivelyPinnedArgs { |
| fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
| let args = <syn::punctuated::Punctuated<Ident, Token![,]>>::parse_terminated(input)?; |
| if args.len() > 1 { |
| return Err(syn::Error::new( |
| input.span(), // not args.span(), as that is only for the first argument. |
| &format!("expected at most 1 argument, got: {}", args.len()), |
| )); |
| } |
| let is_pinned_drop = if let Some(arg) = args.first() { |
| if arg != "PinnedDrop" { |
| return Err(syn::Error::new( |
| arg.span(), |
| "unexpected argument (wasn't `PinnedDrop`)", |
| )); |
| } |
| true |
| } else { |
| false |
| }; |
| Ok(RecursivelyPinnedArgs { is_pinned_drop }) |
| } |
| } |
| |
| /// Prevents this type from being directly created outside of this crate in safe |
| /// code. |
| /// |
| /// For enums and unit structs, this uses the `#[non_exhaustive]` attribute. |
| /// This leads to unfortunate error messages, but there is no other way to |
| /// prevent creation of an enum or a unit struct at this time. |
| /// |
| /// For tuple structs, we also use `#[non_exhaustive]`, as it's no worse than |
| /// the alternative. Both adding a private field and adding `#[non_exhaustive]` |
| /// lead to indirect error messages, but `#[non_exhaustive]` is the more likely |
| /// of the two to ever get custom error message support. |
| /// |
| /// Finally, for structs with named fields, we actually *cannot* use |
| /// `#[non_exhaustive]`, because it would make the struct not FFI-safe, and |
| /// structs with named fields are specifically supported for C++ interop. |
| /// Instead, we use a private field with a name that indicates the error. |
| /// (`__must_use_ctor_to_initialize`). |
| /// |
| /// Unions are not yet implemented properly. |
| /// |
| /// --- |
| /// |
| /// Note that the use of `#[non_exhaustive]` also has other effects. At the |
| /// least: tuple variants and tuple structs marked with `#[non_exhaustive]` |
| /// cannot be pattern matched using the "normal" syntax. Instead, one must use |
| /// curly braces. (Broken: `T(x, ..)`; woken: `T{0: x, ..}`). |
| /// |
| /// (This does not seem very intentional, and with all luck will be fixed before |
| /// too long.) |
| fn forbid_initialization(s: &mut syn::DeriveInput) { |
| let non_exhaustive_attr = syn::parse_quote!(#[non_exhaustive]); |
| match &mut s.data { |
| // TODO(b/232969667): prevent creation of unions from safe code. |
| // (E.g. hide inside a struct.) |
| syn::Data::Union(_) => return, |
| syn::Data::Struct(data) => { |
| match &mut data.fields { |
| syn::Fields::Unit | syn::Fields::Unnamed(_) => { |
| s.attrs.insert(0, non_exhaustive_attr); |
| } |
| syn::Fields::Named(fields) => { |
| fields.named.push(syn::Field { |
| attrs: vec![], |
| vis: syn::Visibility::Inherited, |
| // TODO(jeanpierreda): better hygiene: work even if a field has the same name. |
| ident: Some(Ident::new(FIELD_FOR_MUST_USE_CTOR, Span::call_site())), |
| colon_token: Some(<syn::Token![:]>::default()), |
| ty: syn::parse_quote!([u8; 0]), |
| }); |
| } |
| } |
| } |
| syn::Data::Enum(e) => { |
| // Enums can't have private fields. Instead, we need to add #[non_exhaustive] to |
| // every variant -- this makes it impossible to construct the |
| // variants. |
| for variant in &mut e.variants { |
| variant.attrs.insert(0, non_exhaustive_attr.clone()); |
| } |
| } |
| } |
| } |
| |
| /// `#[recursively_pinned]` pins every field, similar to `#[pin_project]`, and |
| /// marks the struct `!Unpin`. |
| /// |
| /// Example: |
| /// |
| /// ``` |
| /// #[recursively_pinned] |
| /// struct S { |
| /// field: i32, |
| /// } |
| /// ``` |
| /// |
| /// This is analogous to using pin_project, pinning every field, as so: |
| /// |
| /// ``` |
| /// #[pin_project(!Unpin)] |
| /// struct S { |
| /// #[pin] |
| /// field: i32, |
| /// } |
| /// ``` |
| /// |
| /// ## Arguments |
| /// |
| /// ### `PinnedDrop` |
| /// |
| /// To define a destructor for a recursively-pinned struct, pass `PinnedDrop` |
| /// and implement the `PinnedDrop` trait. |
| /// |
| /// `#[recursively_pinned]` prohibits implementing `Drop`, as that would make it |
| /// easy to violate the `Pin` guarantee. Instead, to define a destructor, one |
| /// must define a `PinnedDrop` impl, as so: |
| /// |
| /// ``` |
| /// #[recursively_pinned(PinnedDrop)] |
| /// struct S { |
| /// field: i32, |
| /// } |
| /// |
| /// impl PinnedDrop for S { |
| /// unsafe fn pinned_drop(self: Pin<&mut Self>) { |
| /// println!("I am being destroyed!"); |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// (This is analogous to `#[pin_project(PinnedDrop)]`.) |
| /// |
| /// ## Direct initialization |
| /// |
| /// Use the `ctor!` macro to instantiate recursively pinned types. For example: |
| /// |
| /// ``` |
| /// // equivalent to `let x = Point {x: 3, y: 4}`, but uses pinned construction. |
| /// emplace! { |
| /// let x = ctor!(Point {x: 3, y: 4}); |
| /// } |
| /// ``` |
| /// |
| /// Recursively pinned types cannot be created directly in safe code, as they |
| /// are pinned from the very moment of their creation. |
| /// |
| /// This is prevented either using `#[non_exhaustive]` or using a private field, |
| /// depending on the type in question. For example, enums use |
| /// `#[non_exhaustive]`, and structs with named fields use a private field named |
| /// `__must_use_ctor_to_initialize`. This can lead to confusing error messages, |
| /// so watch out! |
| /// |
| /// ## Supported types |
| /// |
| /// Structs, enums, and unions are all supported. However, unions do not receive |
| /// a `pin_project` method, as there is no way to implement pin projection for |
| /// unions. (One cannot know which field is active.) |
| #[proc_macro_attribute] |
| pub fn recursively_pinned(args: TokenStream, item: TokenStream) -> TokenStream { |
| match recursively_pinned_impl(args.into(), item.into()) { |
| Ok(t) => t.into(), |
| Err(e) => e.into_compile_error().into(), |
| } |
| } |
| |
| /// A separate function for calling from tests. |
| /// |
| /// See e.g. https://users.rust-lang.org/t/procedural-macro-api-is-used-outside-of-a-procedural-macro/30841 |
| fn recursively_pinned_impl( |
| args: proc_macro2::TokenStream, |
| item: proc_macro2::TokenStream, |
| ) -> syn::Result<proc_macro2::TokenStream> { |
| let args = syn::parse2::<RecursivelyPinnedArgs>(args)?; |
| let mut input = syn::parse2::<syn::DeriveInput>(item)?; |
| |
| let project_pin_impl = project_pin_impl(&input)?; |
| let name = input.ident.clone(); |
| |
| // Create two copies of input: one (public) has a private field that can't be |
| // instantiated. The other (only visible via |
| // RecursivelyPinned::CtorInitializedFields) doesn't have this field. |
| // This causes `ctor!(Foo {})` to work, but `Foo{}` to complain of a missing |
| // field. |
| let mut ctor_initialized_input = input.clone(); |
| // Removing repr(C) triggers dead-code detection. |
| ctor_initialized_input.attrs = vec![syn::parse_quote!(#[allow(dead_code)])]; |
| // TODO(jeanpierreda): This should really check for name collisions with any types |
| // used in the fields. Collisions with other names don't matter, because the |
| // type is locally defined within a narrow scope. |
| ctor_initialized_input.ident = syn::Ident::new(&format!("__CrubitCtor{name}"), name.span()); |
| let ctor_initialized_name = &ctor_initialized_input.ident; |
| forbid_initialization(&mut input); |
| |
| let (input_impl_generics, input_ty_generics, input_where_clause) = |
| input.generics.split_for_impl(); |
| |
| let drop_impl = if args.is_pinned_drop { |
| quote! { |
| impl #input_impl_generics Drop for #name #input_ty_generics #input_where_clause { |
| fn drop(&mut self) { |
| unsafe {::ctor::PinnedDrop::pinned_drop(::core::pin::Pin::new_unchecked(self))} |
| } |
| } |
| } |
| } else { |
| quote! { |
| impl #input_impl_generics ::ctor::macro_internal::DoNotImplDrop for #name #input_ty_generics #input_where_clause {} |
| /// A no-op PinnedDrop that will cause an error if the user also defines PinnedDrop, |
| /// due to forgetting to pass `PinnedDrop` to #[recursively_pinned(PinnedDrop)]`. |
| impl #input_impl_generics ::ctor::PinnedDrop for #name #input_ty_generics #input_where_clause { |
| unsafe fn pinned_drop(self: ::core::pin::Pin<&mut Self>) {} |
| } |
| } |
| }; |
| |
| Ok(quote! { |
| #input |
| #project_pin_impl |
| |
| #drop_impl |
| impl #input_impl_generics !Unpin for #name #input_ty_generics #input_where_clause {} |
| |
| // Introduce a new scope to limit the blast radius of the CtorInitializedFields type. |
| // This lets us use relatively readable names: while the impl is visible outside the scope, |
| // type is otherwise not visible. |
| const _ : () = { |
| #ctor_initialized_input |
| |
| unsafe impl #input_impl_generics ::ctor::RecursivelyPinned for #name #input_ty_generics #input_where_clause { |
| type CtorInitializedFields = #ctor_initialized_name #input_ty_generics; |
| } |
| }; |
| }) |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use googletest::prelude::*; |
| use token_stream_matchers::assert_rs_matches; |
| |
| /// Essentially a change detector, but handy for debugging. |
| /// |
| /// At time of writing, we can't write negative compilation tests, so |
| /// asserting on the output is as close as we can get. Once negative |
| /// compilation tests are added, it would be better to test various |
| /// safety features that way. |
| #[gtest] |
| fn test_recursively_pinned_struct() { |
| let definition = |
| recursively_pinned_impl(quote! {}, quote! {#[repr(C)] struct S {x: i32}}).unwrap(); |
| |
| // The struct can't be directly created, but can be created via |
| // CtorInitializedFields: |
| assert_rs_matches!( |
| definition, |
| quote! { |
| #[repr(C)] |
| struct S { |
| x: i32, |
| __must_use_ctor_to_initialize: [u8; 0] |
| } |
| } |
| ); |
| assert_rs_matches!( |
| definition, |
| quote! { |
| const _: () = { |
| #[allow(dead_code)] |
| struct __CrubitCtorS {x: i32} |
| unsafe impl ::ctor::RecursivelyPinned for S { |
| type CtorInitializedFields = __CrubitCtorS; |
| } |
| }; |
| } |
| ); |
| |
| // The type is non-Unpin: |
| assert_rs_matches!( |
| definition, |
| quote! { |
| impl !Unpin for S {} |
| } |
| ); |
| |
| // The remaining features of the generated output are better tested via |
| // real tests that exercise the code. |
| } |
| |
| /// The enum version of `test_recursively_pinned_struct`. |
| #[gtest] |
| fn test_recursively_pinned_enum() { |
| let definition = recursively_pinned_impl( |
| quote! {}, |
| quote! { |
| #[repr(C)] |
| enum E { |
| A, |
| B(i32), |
| } |
| }, |
| ) |
| .unwrap(); |
| |
| // The enum variants can't be directly created, but can be created via |
| // CtorInitializedFields: |
| assert_rs_matches!( |
| definition, |
| quote! { |
| #[repr(C)] |
| enum E { |
| #[non_exhaustive] |
| A, |
| #[non_exhaustive] |
| B(i32), |
| } |
| } |
| ); |
| assert_rs_matches!( |
| definition, |
| quote! { |
| const _: () = { |
| #[allow(dead_code)] |
| enum __CrubitCtorE { |
| A, |
| B(i32), |
| } |
| unsafe impl ::ctor::RecursivelyPinned for E { |
| type CtorInitializedFields = __CrubitCtorE; |
| } |
| }; |
| } |
| ); |
| |
| // The type is non-Unpin: |
| assert_rs_matches!( |
| definition, |
| quote! { |
| impl !Unpin for E {} |
| } |
| ); |
| |
| // The remaining features of the generated output are better tested via |
| // real tests that exercise the code. |
| } |
| } |