use std::str::FromStr;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Field, GenericArgument, Ident, Path, PathArguments, Type};
#[derive(Default)]
pub(crate) struct Config {
pub(crate) for_self: bool,
pub(crate) strip_option: bool,
}
pub(crate) fn self_builder(input: &DeriveInput, config: Config) -> TokenStream {
let name = &input.ident;
let trait_name = format_ident!("{}SelfBuilder", name);
let fields = match &input.data {
Data::Struct(s) => s.fields.iter().map(FieldInfo::from).map(|mut f| {
if config.strip_option {
f.strip_option = true;
}
f
}),
_ => return TokenStream::new(),
};
let impls_signs = fields
.flat_map(|f| construct_impl_sign(f, config.for_self))
.collect::<Vec<_>>();
let mut impls = Vec::with_capacity(impls_signs.len());
let mut signs = Vec::with_capacity(impls_signs.len());
for (impll, sign) in impls_signs {
impls.push(impll);
signs.push(sign);
}
if config.for_self {
(quote! {
impl #name {
#(
#impls
)*
}
})
.into()
} else {
(quote! {
pub trait #trait_name {
#(
#signs
)*
}
impl #trait_name for #name {
#(
#impls
)*
}
})
.into()
}
}
struct FieldInfo {
name: Ident,
ty: Type,
strip_option: bool,
skip_setter: bool,
}
impl From<&Field> for FieldInfo {
fn from(field: &Field) -> Self {
let mut strip_option = false;
let mut skip_setter = false;
for attr in field.attrs.iter() {
match attr.tokens.to_string().as_str() {
"builder(setter(strip_option))" => strip_option = true,
"builder(setter(skip))" => skip_setter = true,
_ => {}
}
}
Self {
name: field.ident.as_ref().expect("Expected a name").clone(),
ty: field.ty.clone(),
strip_option,
skip_setter,
}
}
}
fn construct_impl_sign(
field_info: FieldInfo,
for_self: bool,
) -> Option<(TokenStream2, TokenStream2)> {
let FieldInfo {
name,
ty,
strip_option,
skip_setter,
} = field_info;
let method_name = quote::format_ident!("with_{}", name);
let vis = for_self
.then(|| TokenStream2::from_str("pub").unwrap())
.unwrap_or_else(TokenStream2::new);
if !skip_setter {
let doc_msg = format!("Set the {} field of this struct.", name);
Some(
if let (true, Some(stripped_ty)) = (strip_option, extract_type_from_option(&ty)) {
let impll = quote! {
#[doc = #doc_msg]
#vis fn #method_name(self, #name: impl Into<#stripped_ty>) -> Self {
let mut new = self;
new.#name = Some(#name.into());
new
}
};
let sign = quote! {
#[doc = #doc_msg]
fn #method_name(self, #name: impl Into<#stripped_ty>) -> Self;
};
(impll, sign)
} else {
let impll = quote! {
#[doc = #doc_msg]
#vis fn #method_name(self, #name: impl Into<#ty>) -> Self {
let mut new = self;
new.#name = #name.into();
new
}
};
let sign = quote! {
#[doc = #doc_msg]
fn #method_name(self, #name: impl Into<#ty>) -> Self;
};
(impll, sign)
},
)
} else {
None
}
}
fn extract_type_from_option(ty: &Type) -> Option<Type> {
fn path_is_option(path: &Path) -> bool {
path.segments.last().unwrap().ident == "Option"
}
let t = match ty {
Type::Path(typepath) if typepath.qself.is_none() => {
if !path_is_option(&typepath.path) {
return None;
}
let type_params = &typepath.path.segments.last().unwrap().arguments;
let generic_arg = match type_params {
PathArguments::AngleBracketed(params) => params.args.first().unwrap(),
_ => panic!("TODO: error handling {}:{}", file!(), line!()),
};
match generic_arg {
GenericArgument::Type(ty) => ty.clone(),
_ => panic!("TODO: error handling {}:{}", file!(), line!()),
}
}
_ => panic!("TODO: error handling {}:{}", file!(), line!()),
};
Some(t)
}