use darling::ast::Data;
use darling::util::{Ignored, PathList};
use darling::{FromDeriveInput, FromField, FromMeta};
use proc_macro::TokenStream;
use quote::quote;
use syn::{
AngleBracketedGenericArguments, Attribute, GenericArgument, Generics, Ident, PathArguments,
Type, TypePath, Visibility,
};
use crate::utils::{filter_forward_attrs, ForwardAttrsFilter};
#[derive(Debug, FromMeta)]
struct RequiredArgs {
ident: Ident,
derive: Option<PathList>,
#[darling(default)]
forward_attrs: ForwardAttrsFilter,
}
#[derive(Debug, FromField)]
#[darling(attributes(required), forward_attrs)]
struct RequiredField {
ident: Option<Ident>,
vis: Visibility,
ty: Type,
attrs: Vec<Attribute>,
#[darling(default)]
forward_attrs: ForwardAttrsFilter,
}
#[derive(Debug, FromDeriveInput)]
#[darling(
attributes(required),
forward_attrs,
supports(struct_named, struct_tuple)
)]
struct RequiredInput {
ident: Ident,
vis: Visibility,
generics: Generics,
data: Data<Ignored, RequiredField>,
attrs: Vec<Attribute>,
#[darling(flatten)]
args: RequiredArgs,
}
pub fn required(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
let input = match RequiredInput::from_derive_input(&input) {
Ok(input) => input,
Err(err) => {
return TokenStream::from(err.write_errors());
}
};
let derive_attr = input.args.derive.as_ref().map(|derives| {
let derives = derives.iter();
quote! {
#[derive(#(#derives),*)]
}
});
let forward_attrs = filter_forward_attrs(input.attrs.iter(), &input.args.forward_attrs);
let vis = input.vis;
let _ident = input.ident;
let required_ident = input.args.ident;
let generics = input.generics;
let fields = input.data.take_struct().unwrap();
let mut field_idents = Vec::new();
let mut field_declares = Vec::new();
fields.fields.iter().for_each(|field| {
let vis = &field.vis;
let ident = field.ident.as_ref().unwrap();
let forward_attrs = filter_forward_attrs(
field.attrs.iter(),
&field.forward_attrs + &input.args.forward_attrs,
);
let ty = &field.ty;
let ty = match ty {
Type::Path(TypePath { path, .. }) => {
let segments_str = &path
.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect::<Vec<_>>()
.join("::");
let option_segment = ["Option", "std::option::Option", "core::option::Option"]
.iter()
.find(|s| segments_str == *s)
.and_then(|_| path.segments.last());
let inner_type = option_segment
.and_then(|path_seg| match &path_seg.arguments {
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
args,
..
}) => args.first(),
_ => None,
})
.and_then(|generic_arg| match generic_arg {
GenericArgument::Type(ty) => Some(ty),
_ => None,
});
inner_type.unwrap_or(ty)
}
_ => ty,
};
field_idents.push(ident.clone());
field_declares.push(quote! {
#(#forward_attrs)*
#vis #ident: #ty
});
});
quote! {
#derive_attr
#(#forward_attrs)*
#vis struct #required_ident #generics {
#(#field_declares),*
}
}
.into()
}