use quote::{format_ident, quote};
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
mod field_kind;
mod helpers;
use field_kind::FieldKind;
#[proc_macro_derive(IntoOwned)]
pub fn into_owned(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let expanded = impl_with_generator(&ast, IntoOwnedGen);
TokenStream::from(expanded)
}
#[proc_macro_derive(Borrowed)]
pub fn borrowed(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let expanded = impl_with_generator(&ast, BorrowedGen);
TokenStream::from(expanded)
}
fn impl_with_generator<G: BodyGenerator>(
ast: &syn::DeriveInput,
gen: G,
) -> proc_macro2::TokenStream {
let name = &ast.ident;
let borrowed_params = gen.quote_borrowed_params(ast);
let borrowed = if borrowed_params.is_empty() {
quote! {}
} else {
quote! { < #(#borrowed_params),* > }
};
let params = gen.quote_type_params(ast);
let params = if params.is_empty() {
quote! {}
} else {
quote! { < #(#params),* > }
};
let owned_params = gen.quote_rhs_params(ast);
let owned = if owned_params.is_empty() {
quote! {}
} else {
quote! { < #(#owned_params),* > }
};
let body = match ast.data {
syn::Data::Struct(ref body) => {
let inner = gen.visit_struct(body);
quote! { #name #inner }
}
syn::Data::Enum(ref body) => {
let cases = body.variants.iter().map(|variant| {
let unqualified_ident = &variant.ident;
let ident = quote! { #name::#unqualified_ident };
gen.visit_enum_data(ident, variant)
});
quote! { match self { #(#cases),* } }
}
syn::Data::Union(_) => todo!("unions are not supported (5)"),
};
gen.combine_impl(borrowed, name, params, owned, body)
}
trait BodyGenerator {
fn quote_borrowed_params(&self, ast: &syn::DeriveInput) -> Vec<proc_macro2::TokenStream> {
let borrowed_lifetime_params = ast.generics.lifetimes().map(|alpha| quote! { #alpha });
let borrowed_type_params = ast.generics.type_params().map(|ty| quote! { #ty });
borrowed_lifetime_params
.chain(borrowed_type_params)
.collect::<Vec<_>>()
}
fn quote_type_params(&self, ast: &syn::DeriveInput) -> Vec<proc_macro2::TokenStream> {
ast.generics
.lifetimes()
.map(|alpha| quote! { #alpha })
.chain(ast.generics.type_params().map(|ty| {
let ident = &ty.ident;
quote! { #ident }
}))
.collect::<Vec<_>>()
}
fn quote_rhs_params(&self, ast: &syn::DeriveInput) -> Vec<proc_macro2::TokenStream> {
let owned_lifetime_params = ast.generics.lifetimes().map(|_| quote! { 'static });
let owned_type_params = ast.generics.type_params().map(|ty| {
let ident = &ty.ident;
quote! { #ident }
});
owned_lifetime_params
.chain(owned_type_params)
.collect::<Vec<_>>()
}
fn visit_struct(&self, data: &syn::DataStruct) -> proc_macro2::TokenStream;
fn visit_enum_data(
&self,
variant: proc_macro2::TokenStream,
data: &syn::Variant,
) -> proc_macro2::TokenStream;
fn combine_impl(
&self,
borrows: proc_macro2::TokenStream,
name: &syn::Ident,
rhs_params: proc_macro2::TokenStream,
owned: proc_macro2::TokenStream,
body: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream;
}
struct IntoOwnedGen;
impl BodyGenerator for IntoOwnedGen {
fn visit_struct(&self, data: &syn::DataStruct) -> proc_macro2::TokenStream {
enum Fields {
Named,
Tuple,
Unit,
}
use Fields::*;
let fields_kind = data
.fields
.iter()
.next()
.map(|field| if field.ident.is_some() { Named } else { Tuple })
.unwrap_or(Unit);
match fields_kind {
Named => {
let fields = data.fields.iter().map(|field| {
let ident = field.ident.as_ref().expect("unexpected unnamed field");
let field_ref = quote! { self.#ident };
let code = FieldKind::resolve(&field.ty).move_or_clone_field(&field_ref);
quote! { #ident: #code }
});
quote! { { #(#fields),* } }
}
Tuple => {
let fields = data.fields.iter().enumerate().map(|(index, field)| {
let index = syn::Index::from(index);
let index = quote! { self.#index };
FieldKind::resolve(&field.ty).move_or_clone_field(&index)
});
quote! { ( #(#fields),* ) }
}
Unit => {
quote! {}
}
}
}
fn visit_enum_data(
&self,
ident: proc_macro2::TokenStream,
variant: &syn::Variant,
) -> proc_macro2::TokenStream {
if variant.fields.is_empty() {
return quote!(#ident => #ident);
}
let fields_are_named = variant.fields.iter().any(|field| field.ident.is_some());
if fields_are_named {
let named_fields = variant
.fields
.iter()
.filter_map(|field| field.ident.as_ref());
let cloned = variant.fields.iter().map(|field| {
let ident = field.ident.as_ref().unwrap();
let ident = quote!(#ident);
let code = FieldKind::resolve(&field.ty).move_or_clone_field(&ident);
quote! { #ident: #code }
});
quote! { #ident { #(#named_fields),* } => #ident { #(#cloned),* } }
} else {
let unnamed_fields = &variant
.fields
.iter()
.enumerate()
.filter(|(_, field)| field.ident.is_none())
.map(|(index, _)| format_ident!("arg_{}", index))
.collect::<Vec<_>>();
let cloned = unnamed_fields
.iter()
.zip(variant.fields.iter())
.map(|(ident, field)| {
let ident = quote! { #ident };
FieldKind::resolve(&field.ty).move_or_clone_field(&ident)
})
.collect::<Vec<_>>();
quote! { #ident ( #(#unnamed_fields),* ) => #ident ( #(#cloned),* ) }
}
}
fn combine_impl(
&self,
borrowed: proc_macro2::TokenStream,
name: &syn::Ident,
params: proc_macro2::TokenStream,
owned: proc_macro2::TokenStream,
body: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
quote! {
impl #borrowed #name #params {
pub fn into_owned(self) -> #name #owned { #body }
}
}
}
}
struct BorrowedGen;
impl BodyGenerator for BorrowedGen {
fn quote_rhs_params(&self, ast: &syn::DeriveInput) -> Vec<proc_macro2::TokenStream> {
let owned_lifetime_params = ast.generics.lifetimes().map(|_| quote! { '__borrowedgen });
let owned_type_params = ast.generics.type_params().map(|ty| {
let ident = &ty.ident;
quote! { #ident }
});
owned_lifetime_params
.chain(owned_type_params)
.collect::<Vec<_>>()
}
fn visit_struct(&self, data: &syn::DataStruct) -> proc_macro2::TokenStream {
let fields = data.fields.iter().map(|field| {
let ident = field.ident.as_ref().expect("this fields has no ident (4)");
let field_ref = quote! { self.#ident };
let code = FieldKind::resolve(&field.ty).borrow_or_clone(&field_ref);
quote! { #ident: #code }
});
quote! { { #(#fields),* } }
}
fn visit_enum_data(
&self,
ident: proc_macro2::TokenStream,
variant: &syn::Variant,
) -> proc_macro2::TokenStream {
if variant.fields.is_empty() {
return quote!(#ident => #ident);
}
let fields_are_named = variant.fields.iter().any(|field| field.ident.is_some());
if fields_are_named {
let idents = variant
.fields
.iter()
.map(|field| field.ident.as_ref().expect("this fields has no ident (5)"));
let cloned = variant.fields.iter().map(|field| {
let ident = field.ident.as_ref().expect("this fields has no ident (6)");
let ident = quote! { #ident };
let code = FieldKind::resolve(&field.ty).borrow_or_clone(&ident);
quote! { #ident: #code }
});
quote! { #ident { #(ref #idents),* } => #ident { #(#cloned),* } }
} else {
let idents = (0..variant.fields.len())
.map(|index| quote::format_ident!("x{}", index))
.collect::<Vec<_>>();
let cloned = idents
.iter()
.zip(variant.fields.iter())
.map(|(ident, field)| {
let ident = quote! { #ident };
FieldKind::resolve(&field.ty).borrow_or_clone(&ident)
})
.collect::<Vec<_>>();
quote! { #ident ( #(ref #idents),* ) => #ident ( #(#cloned),* ) }
}
}
fn combine_impl(
&self,
borrowed: proc_macro2::TokenStream,
name: &syn::Ident,
params: proc_macro2::TokenStream,
owned: proc_macro2::TokenStream,
body: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
quote! {
impl #borrowed #name #params {
pub fn borrowed<'__borrowedgen>(&'__borrowedgen self) -> #name #owned { #body }
}
}
}
}