use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Fields, ItemStruct, parse_macro_input, parse_quote};
mod context;
use syn::DeriveInput;
use crate::context::{IS_SERDE_ENABLED, crate_path, has_patchable_skip_attr};
const IS_IMPL_FROM_ENABLED: bool = cfg!(feature = "impl_from");
#[proc_macro_attribute]
pub fn patchable_model(_attr: TokenStream, item: TokenStream) -> TokenStream {
let crate_path = crate_path();
let derives = if IS_SERDE_ENABLED {
parse_quote! {
#[derive(#crate_path::Patchable, #crate_path::Patch, ::serde::Serialize)]
}
} else {
parse_quote! {
#[derive(#crate_path::Patchable, #crate_path::Patch)]
}
};
let mut input = parse_macro_input!(item as ItemStruct);
input.attrs.push(derives);
if IS_SERDE_ENABLED {
add_serde_skip_attrs(&mut input.fields);
}
(quote! { #input }).into()
}
#[proc_macro_derive(Patchable, attributes(patchable))]
pub fn derive_patchable(input: TokenStream) -> TokenStream {
expand(input, |ctx| {
let patch_struct_def = ctx.build_patch_struct();
let patchable_trait_impl = ctx.build_patchable_trait_impl();
let from_struct_impl = IS_IMPL_FROM_ENABLED.then(|| {
let from_struct_impl = ctx.build_from_trait_impl();
quote! {
#[automatically_derived]
#from_struct_impl
}
});
quote! {
const _: () = {
#[automatically_derived]
#patch_struct_def
#[automatically_derived]
#patchable_trait_impl
#from_struct_impl
};
}
})
}
#[proc_macro_derive(Patch, attributes(patchable))]
pub fn derive_patch(input: TokenStream) -> TokenStream {
expand(input, |ctx| {
let patch_trait_impl = ctx.build_patch_trait_impl();
quote! {
const _: () = {
#[automatically_derived]
#patch_trait_impl
};
}
})
}
fn expand<F>(input: TokenStream, f: F) -> TokenStream
where
F: FnOnce(&context::MacroContext) -> TokenStream2,
{
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
match context::MacroContext::new(&input) {
Ok(ctx) => f(&ctx).into(),
Err(e) => e.to_compile_error().into(),
}
}
fn add_serde_skip_attrs(fields: &mut Fields) {
for field in fields.iter_mut() {
if has_patchable_skip_attr(field) {
field.attrs.push(parse_quote! { #[serde(skip)] });
}
}
}