blob: 6c38e60eec4c3b42d18b8ffc81791baad2b9eff4 [file] [log] [blame]
// 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
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{quote, quote_spanned, ToTokens as _};
use syn::spanned::Spanned as _;
// TODO(jeanpierreda): derive constructors and assignment for copy and move.
#[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 filled_fields: Vec<_> = match &input.data {
syn::Data::Struct(data) => {
if let syn::Fields::Unit = data.fields {
// The generated code will not work as is for `struct Foo;` because we would
// create the literal `Foo {}`, which is not valid.
// TODO(jeanpierreda): special-case this.
// Note: this is not important right now, because pin_project doesn't support
// fieldless structs to begin with, so either way it fails to compile.
// We could generate the code for Foo {} and have it fail inside pin_project,
// but if pin_project ever added support for empty structs, this
// would unexpectedly start working, but with unexpected
// results.
todo!();
}
data.fields
.iter()
.enumerate()
.map(|(i, field)| {
let field_i = syn::Index::from(i);
let field_name = match &field.ident {
None => quote! {#field_i},
Some(name) => quote! {#name},
};
let field_type = &field.ty;
quote_spanned! {field.span() =>
#field_name: <#field_type as ::ctor::CtorNew<()>>::ctor_new(())
}
})
.collect()
}
_ => unimplemented!(),
};
let expanded = quote! {
struct #struct_ctor_name();
impl ::ctor::Ctor for #struct_ctor_name {
type Output = #struct_name;
unsafe fn ctor(self, dest: ::std::pin::Pin<&mut ::std::mem::MaybeUninit<Self::Output>>) {
::ctor::ctor!(
#struct_name { #(#filled_fields),* }
).ctor(dest)
}
}
impl !::std::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)
}
/// #[recursively_pinned] pins every field using #[pin_project]. All macro
/// arguments are forwarded.
///
/// To use this macro, you must depend directly on pin_project. (This is due to
/// limitations of procedural macros, which do not have a `$crate` equivalent.)
///
/// Example:
///
/// ```
/// #[recursively_pinned]
/// struct S {
/// field: i32,
/// }
/// ```
///
/// This is equivalent to using pin_project directly, but pinning every field,
/// as so:
///
/// ```
/// #[pin_project]
/// struct S {
/// #[pin]
/// field: i32,
/// }
/// ```
///
/// Note that recursively_pinned doesn't mark a struct as `!Unpin` -- to do
/// that, use a `!Unpin` member such as `PhantomPinned` member (initialized via
/// e.g. `PhantomPinnedCtor`), or pass in `UnsafeUnpin`.
#[proc_macro_attribute]
pub fn recursively_pinned(args: TokenStream, item: TokenStream) -> TokenStream {
let args: proc_macro2::TokenStream = args.into();
let mut input = syn::parse_macro_input!(item as syn::DeriveInput);
input.attrs.insert(0, syn::parse_quote!(#[::pin_project::pin_project(#args)]));
let pin: syn::Attribute = syn::parse_quote!(#[pin]);
match &mut input.data {
syn::Data::Struct(data) => {
for field in &mut data.fields {
field.attrs.push(pin.clone());
}
}
syn::Data::Enum(e) => {
for variant in &mut e.variants {
for field in &mut variant.fields {
field.attrs.push(pin.clone());
}
}
}
syn::Data::Union(_) => unimplemented!(),
}
let name = input.ident.clone();
let input = input.into_token_stream();
let expanded = quote! {
#input
unsafe impl ::ctor::RecursivelyPinned for #name {}
};
TokenStream::from(expanded)
}