use std::str::FromStr;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::parse::{Parse, ParseStream};
use syn::{DeriveInput, Error, GenericArgument, Ident, Path, PathArguments, Result, Type};
type BuilderField = (Ident, Box<Type>);
pub struct OptStruct {
name: Ident,
required_args: Vec<BuilderField>,
optional_args: Vec<BuilderField>,
}
impl Parse for OptStruct {
fn parse(input: ParseStream) -> Result<Self> {
let input: DeriveInput = input.parse()?;
let data = match &input.data {
syn::Data::Struct(a) => Ok(a),
syn::Data::Enum(_) | syn::Data::Union(_) => Err(syn::Error::new(
input.ident.span(),
"Only structs can be created with the optional pattern.",
)),
}?;
let name = input.ident.clone();
let mut parsing_optionals = false;
let (mut required_args, mut optional_args) = (Vec::new(), Vec::new());
for field in &data.fields {
let syn::Field { ident, ty, .. } = field;
let ident = ident.clone().ok_or(Error::new_spanned(
&name,
"Non-optional values must be placed before optionals",
))?;
let is_optional = match ty {
Type::Path(p) => {
if let Some(arg) = p.path.segments.first() {
arg.ident.to_string() == "Option"
} else {
false
}
}
_ => false,
};
match (is_optional, parsing_optionals) {
(true, _) => {
optional_args.push((ident.clone(), extract_type_from_option(ty)?.clone()));
parsing_optionals = true;
}
(false, false) => required_args.push((ident.clone(), Box::new(ty.clone()))),
(false, true) => {
return Err(Error::new_spanned(
&name,
"Non-optional values must be placed before optionals",
));
}
};
}
Ok(Self {
name,
optional_args,
required_args,
})
}
}
impl ToTokens for OptStruct {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let OptStruct {
required_args,
optional_args,
name,
..
} = self;
let helper_defs = required_args
.iter()
.chain(optional_args.iter())
.enumerate()
.map(|(id, (arg, _ty))| {
let id = syn::Index::from(id);
quote! {
(@setter_helper $src:ident #arg $key:ident) => {
$src.#id = Some($key);
};
(@setter_helper $src:ident #arg $key:ident $value:expr) => {
$src.#id = Some($value);
};
}
});
let inners_body = required_args
.iter()
.chain(optional_args.iter())
.map(|_| quote! {None,});
let call_body = required_args
.iter()
.map(|f| (true, f))
.chain(optional_args.iter().map(|f| (false, f)))
.enumerate()
.map(|(id, (required, (name, _ty)))| {
let id = syn::Index::from(id);
match required {
true => quote! {
#name: inners.#id.unwrap(),
},
false => quote! {
#name: inners.#id,
},
}
});
let validator =
GenericGenerator::new(required_args.len()).generate(required_args, optional_args);
ToTokens::to_tokens(
"e! {
#[doc(hidden)]
#[macro_export]
macro_rules! #name {
($($key:ident $(: $value:expr)? ), * $(,)?) => {
{
#[allow(unused_mut)]
let mut inners = (#( #inners_body )*);
{ $( #name! (@setter_helper inners $key $key $($value)? ); )* }
#validator
#[allow(unused_mut)]
let mut validator = Validator::builder();
validator $(.$key())* .build();
#name{
#( #call_body )*
}
}
};
#( #helper_defs )*
}
},
tokens,
);
}
}
fn extract_type_from_option(ty: &Type) -> Result<Box<Type>> {
fn path_is_option(path: &Path) -> bool {
path.leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments.iter().next().unwrap().ident == "Option"
}
match ty {
Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {
let type_params = &typepath.path.segments.iter().next().unwrap().arguments;
let generic_arg = match type_params {
PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),
_ => panic!("TODO: error handling"),
};
match generic_arg {
GenericArgument::Type(ty) => Ok(Box::new(ty.clone())),
_ => panic!("TODO: error handling"),
}
}
_ => panic!("TODO: error handling"),
}
}
struct GenericGenerator {
num_args: usize,
}
impl GenericGenerator {
fn new(num_args: usize) -> Self {
Self { num_args }
}
fn gen_all_generic(&self, exclude: usize) -> TokenStream2 {
let mut inner = quote! {};
for id in 0..self.num_args {
let idref = TokenStream2::from_str(format!("M{}", id).as_str()).unwrap();
if id != exclude {
inner.append_all(quote! { const #idref: bool, });
}
}
quote! { <#inner> }
}
fn gen_all(&self, marker: bool) -> TokenStream2 {
let mut inner = quote! {};
for _ in 0..self.num_args {
inner.append_all(quote! { #marker, });
}
quote! { <#inner> }
}
fn gen_positional(&self, position: usize, marker: bool) -> TokenStream2 {
let mut inner = quote! {};
for id in 0..self.num_args {
if id == position {
inner.append_all(quote! { #marker, });
} else {
let mtok = TokenStream2::from_str(format!("M{}", id).as_str()).unwrap();
inner.append_all(quote! { #mtok, });
}
}
quote! { <#inner> }
}
fn generate(
&self,
required_args: &Vec<BuilderField>,
optional_args: &Vec<BuilderField>,
) -> TokenStream2 {
let impl_generics = self.gen_all_generic(usize::MAX);
let ty_gen = self.gen_all(false);
let builder_builder = quote! {
#[derive(Default)]
struct Validator #impl_generics;
impl Validator #ty_gen {
fn builder() -> Validator #ty_gen { Validator::default() }
}
};
let mut builders = TokenStream2::new();
for (id, (name, _ty)) in required_args.iter().enumerate() {
let impl_generics = self.gen_all_generic(id);
let ty_gen_in = self.gen_positional(id, false);
let ty_gen_out = self.gen_positional(id, true);
builders.append_all(quote! {
impl #impl_generics Validator #ty_gen_in {
fn #name(self) -> Validator #ty_gen_out { unsafe {::core::mem::transmute(self)} }
}
})
}
let impl_generics = self.gen_all_generic(usize::MAX);
let ty_gen = self.gen_positional(usize::MAX, false);
for (name, _ty) in optional_args {
builders.append_all(quote! {
impl #impl_generics Validator #ty_gen {
#[allow(unused)]
fn #name(self) -> Validator #ty_gen { self }
}
})
}
let ty_gen = self.gen_all(true);
let caller = quote! {
impl Validator #ty_gen {
fn build(self) {}
}
};
quote! {
#builder_builder
#builders
#caller
}
}
}