use std::str::FromStr;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::parse::{Parse, ParseStream};
use syn::{Error, FnArg, GenericArgument, Ident, ItemFn, Path, PathArguments, Result, Type};
type BuilderField = (Ident, Box<Type>);
pub struct OptFn {
original: ItemFn,
required_args: Vec<BuilderField>,
optional_args: Vec<BuilderField>,
name: Ident,
}
impl Parse for OptFn {
fn parse(input: ParseStream) -> Result<Self> {
let orig: ItemFn = input.parse()?;
let mut parsing_optionals = false;
let (mut required_args, mut optional_args) = (Vec::new(), Vec::new());
for arg in orig.sig.inputs.clone() {
match arg {
FnArg::Typed(arg) => Ok(arg),
FnArg::Receiver(r) => Err(Error::new_spanned(r, "optfn cannot be used on methods")),
}
.and_then(|f| match (&f).pat.as_ref() {
syn::Pat::Ident(iden) => Ok((iden.clone(), f)),
other => Err(Error::new_spanned(other, "optfn cannot struct fields")),
})
.map(|(name, pat)| {
let is_optional = match pat.ty.as_ref() {
Type::Path(p) => {
if let Some(arg) = p.path.segments.first() {
arg.ident.to_string() == "Option"
} else {
false
}
}
_ => false,
};
(name, pat, is_optional)
})
.and_then(|(name, pat, is_optional)| {
match (is_optional, parsing_optionals) {
(false, false) => {
required_args.push((name.ident, pat.ty));
Ok(())
}
(false, true) => Err(Error::new_spanned(
name,
"Non-optional values must be placed before optionals",
)),
(true, _) => {
optional_args.push((name.ident, extract_type_from_option(pat.ty)?));
parsing_optionals = true;
Ok(())
}
}
})?;
}
Ok(Self {
name: orig.sig.ident.clone(),
original: orig,
required_args,
optional_args,
})
}
}
impl ToTokens for OptFn {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let OptFn {
original,
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 = ::core::option::Option::Some($key);
};
(@setter_helper $src:ident #arg $key:ident $value:expr) => {
$src.#id = ::core::option::Option::Some($value);
};
}
});
let inners_body = required_args
.iter()
.chain(optional_args.iter())
.map(|_| quote! {::core::option::Option::None,});
let call_body = required_args
.iter()
.map(|f| (true, f))
.chain(optional_args.iter().map(|f| (false, f)))
.enumerate()
.map(|(id, (required, (_, ty)))| {
let id = syn::Index::from(id);
match required {
true => quote! {inners.#id.unwrap() as #ty,},
false => quote! { inners.#id, },
}
});
let validator =
GenericGenerator::new(required_args.len()).generate(required_args, optional_args);
let ty_expanse = required_args
.iter()
.chain(optional_args.iter())
.map(|(_, ty)| quote! { ::core::option::Option<#ty>, });
ToTokens::to_tokens(
"e! {
#original
#[doc(hidden)]
#[macro_export]
macro_rules! #name {
($($key:ident $(: $value:expr)? ), * $(,)?) => {
{
#[allow(unused_mut)]
let mut inners: (#( #ty_expanse)*) = (#( #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: Box<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.as_ref() {
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
}
}
}