#![allow(clippy::needless_doctest_main)]
use proc_macro::TokenStream;
use darling::FromField;
use darling::util::Flag;
use quote::{quote, quote_spanned};
use syn::{
parse_macro_input, spanned::Spanned, Data, DataStruct, DeriveInput, Fields, FieldsNamed, Ident,
Lifetime, LifetimeParam,
};
#[derive(darling::FromField)]
#[darling(attributes(destructure))]
struct Attributes {
skip: Flag,
}
#[proc_macro_derive(Destructure, attributes(destructure))]
pub fn derive_destructure(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let generics = &ast.generics;
let generate = format!("Destruct{}", name);
let generate_ident = Ident::new(&generate, name.span());
let fields = if let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { ref named, .. }),
..
}) = ast.data
{
named
} else {
return quote_spanned! { name.span() => compile_error!("Only structures with named fields are supported.") }.into();
};
let destruction = fields.iter().map(|field| {
let Ok(attr) = Attributes::from_field(field) else {
return quote_spanned! { field.span() => compile_error!("unrecognized attribute.") }.into();
};
let name = &field.ident;
let ty = &field.ty;
if attr.skip.is_present() {
quote! {
#name: #ty
}
} else {
quote! {
pub #name: #ty
}
}
});
let constructor = fields.iter().map(|field| {
let name = &field.ident;
quote! {
#name: self.#name
}
});
let freeze = constructor.clone();
let q = quote::quote! {
pub struct #generate_ident #generics {
#(#destruction,)*
}
impl #generics #name #generics {
pub fn into_destruct(self) -> #generate_ident #generics {
#generate_ident { #(#constructor,)* }
}
pub fn reconstruct(self, f: impl FnOnce(&mut #generate_ident #generics)) -> Self {
let mut dest = self.into_destruct();
f(&mut dest);
dest.freeze()
}
pub fn try_reconstruct<E>(self, f: impl FnOnce(&mut #generate_ident #generics) -> Result<(), E>) -> Result<Self, E> {
let mut dest = self.into_destruct();
f(&mut dest)?;
Ok(dest.freeze())
}
}
impl #generics #generate_ident #generics {
pub fn freeze(self) -> #name #generics {
#name { #(#freeze,)* }
}
}
};
q.into()
}
#[proc_macro_derive(DestructureRef)]
pub fn derive_destructure_ref(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let generics = &ast.generics;
let mut destructure_generics = ast.generics.clone();
let generate = format!("Destruct{}Ref", name);
let generate_ident = Ident::new(&generate, name.span());
let fields = if let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { ref named, .. }),
..
}) = ast.data
{
named
} else {
return quote_spanned! { name.span() => compile_error!("Only structures with named fields are supported.") }.into();
};
let origin_lifetime: Lifetime =
syn::parse_str("'__origin_destruct_lifetime").expect("cannot parse lifetime");
destructure_generics
.params
.push(syn::GenericParam::Lifetime(LifetimeParam {
lifetime: origin_lifetime.clone(),
attrs: Default::default(),
colon_token: Default::default(),
bounds: Default::default(),
}));
let destruction = fields.iter().map(|field| {
let name = &field.ident;
let ty = &field.ty;
quote! {
pub #name: & #origin_lifetime #ty
}
});
let expanded = fields.iter().map(|field| {
let name = &field.ident;
quote! {
#name: & self.#name
}
});
let q = quote::quote! {
pub struct #generate_ident #destructure_generics {
#(#destruction,)*
}
impl #generics #name #generics {
pub fn as_destruct<#origin_lifetime>(& #origin_lifetime self) -> #generate_ident #destructure_generics {
#generate_ident { #(#expanded,)* }
}
}
};
q.into()
}
#[proc_macro_derive(Mutation)]
pub fn derive_mutation(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let generics = &ast.generics;
let generate = format!("{}Mut", name);
let generate_ident = Ident::new(&generate, name.span());
let fields = if let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { ref named, .. }),
..
}) = ast.data
{
named
} else {
return quote_spanned! { name.span() => compile_error!("Only structures with named fields are supported.") }.into();
};
let lifetime = Lifetime::new("'mutation", generics.span());
let generics_gn = generics.params.iter();
let generics_with_lt = quote! {
<#lifetime, #(#generics_gn,)*>
};
let destruction = fields.iter().map(|field| {
let name = &field.ident;
let ty = &field.ty;
quote! {
pub #name: &'mutation mut #ty
}
});
let expanded = fields.iter().map(|field| {
let name = &field.ident;
quote! {
#name: &mut self.#name
}
});
let expanded_cloned = expanded.clone();
let q = quote::quote! {
pub struct #generate_ident #generics_with_lt {
#(#destruction,)*
}
impl #generics #name #generics {
pub fn substitute(&mut self, mut f: impl FnOnce(&mut #generate_ident #generics)) {
f(&mut #generate_ident {
#(#expanded,)*
})
}
pub fn try_substitute<E>(&mut self, mut f: impl FnOnce(&mut #generate_ident #generics) -> Result<(), E>) -> Result<(), E> {
f(&mut #generate_ident {
#(#expanded_cloned,)*
})
}
}
};
q.into()
}