blob: 2b69bd28f9297226b6fa05f8e242dc30b4bdce6c [file] [edit]
use proc_macro::{self, TokenStream};
use proc_macro2::Span;
use quote::quote;
use syn::{parse_macro_input, parse_quote, Data, DataEnum, DeriveInput, Field, Fields, Ident, Member};
pub(crate) fn derive_into_owned(input: TokenStream) -> TokenStream {
let DeriveInput {
ident: self_name,
data,
mut generics,
..
} = parse_macro_input!(input);
let res = match data {
Data::Struct(s) => {
let fields = s
.fields
.iter()
.enumerate()
.map(|(index, Field { ident, .. })| {
let name = ident
.as_ref()
.map_or_else(|| Member::Unnamed(index.into()), |ident| Member::Named(ident.clone()));
let value = into_owned(quote! { self.#name });
if let Some(ident) = ident {
quote! { #ident: #value }
} else {
value
}
})
.collect::<Vec<proc_macro2::TokenStream>>();
match s.fields {
Fields::Unnamed(_) => {
quote! {
#self_name(#(#fields),*)
}
}
Fields::Named(_) => {
quote! {
#self_name { #(#fields),* }
}
}
Fields::Unit => quote! {},
}
}
Data::Enum(DataEnum { variants, .. }) => {
let variants = variants
.iter()
.map(|variant| {
let name = &variant.ident;
let mut field_names = Vec::new();
let mut static_fields = Vec::new();
for (index, Field { ident, .. }) in variant.fields.iter().enumerate() {
let name = ident.as_ref().map_or_else(
|| Ident::new(&format!("_{}", index), Span::call_site()),
|ident| ident.clone(),
);
field_names.push(name.clone());
let value = into_owned(quote! { #name });
static_fields.push(if let Some(ident) = ident {
quote! { #ident: #value }
} else {
value
})
}
match variant.fields {
Fields::Unnamed(_) => {
quote! {
#self_name::#name(#(#field_names),*) => {
#self_name::#name(#(#static_fields),*)
}
}
}
Fields::Named(_) => {
quote! {
#self_name::#name { #(#field_names),* } => {
#self_name::#name { #(#static_fields),* }
}
}
}
Fields::Unit => quote! {
#self_name::#name => #self_name::#name,
},
}
})
.collect::<proc_macro2::TokenStream>();
quote! {
match self {
#variants
}
}
}
_ => {
panic!("can only derive IntoOwned for enums and structs")
}
};
let orig_generics = generics.clone();
// Add generic bounds for all type parameters.
let mut type_param_names = vec![];
for ty in generics.type_params() {
type_param_names.push(ty.ident.clone());
}
for type_param in type_param_names {
generics.make_where_clause().predicates.push_value(parse_quote! {
#type_param: ::static_self::IntoOwned<'any>
})
}
let has_lifetime = generics
.params
.first()
.map_or(false, |v| matches!(v, syn::GenericParam::Lifetime(..)));
let has_generic = !generics.params.is_empty();
// Prepend `'any` to generics
let any = syn::GenericParam::Lifetime(syn::LifetimeDef {
attrs: Default::default(),
lifetime: syn::Lifetime {
apostrophe: Span::call_site(),
ident: Ident::new("any", Span::call_site()),
},
colon_token: None,
bounds: Default::default(),
});
generics.params.insert(0, any.clone());
let (impl_generics, _, where_clause) = generics.split_for_impl();
let (_, ty_generics, _) = orig_generics.split_for_impl();
let into_owned = if !has_generic {
quote! {
impl #impl_generics ::static_self::IntoOwned<'any> for #self_name #ty_generics #where_clause {
type Owned = Self;
#[inline]
fn into_owned(self) -> Self {
self
}
}
}
} else {
let mut generics_without_default = generics.clone();
let mut params = Vec::new();
for p in generics_without_default.params.iter_mut() {
if let syn::GenericParam::Type(ty) = p {
ty.default = None;
params.push(quote!(<#ty as static_self::IntoOwned<'any>>::Owned));
}
}
if has_lifetime {
quote! {
impl #impl_generics ::static_self::IntoOwned<'any> for #self_name #ty_generics #where_clause {
type Owned = #self_name<'any, #(#params),*>;
/// Consumes the value and returns an owned clone.
fn into_owned(self) -> Self::Owned {
use ::static_self::IntoOwned;
#res
}
}
}
} else {
quote! {
impl #impl_generics ::static_self::IntoOwned<'any> for #self_name #ty_generics #where_clause {
type Owned = #self_name<#(#params),*>;
/// Consumes the value and returns an owned clone.
fn into_owned(self) -> Self::Owned {
use ::static_self::IntoOwned;
#res
}
}
}
}
};
into_owned.into()
}
fn into_owned(name: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
quote! { #name.into_owned() }
}