use proc_macro2::TokenStream;
use syn::DeriveInput;
use syn::spanned::Spanned;
use syn::parse::Error;
use quote::quote;
use default_attr::{DefaultAttr, ConversionStrategy};
use util::find_only;
pub fn impl_my_derive(input: &DeriveInput) -> Result<TokenStream, Error> {
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let (default_expr, doc) = match input.data {
syn::Data::Struct(ref body) => {
let (body_assignment, doc) = default_body_tt(&body.fields)?;
(quote! {
#name #body_assignment
}, format!("Return `{}{}`", name, doc))
}
syn::Data::Enum(ref body) => {
let default_variant = find_only(body.variants.iter(), |variant| {
if let Some(meta) = DefaultAttr::find_in_attributes(&variant.attrs)? {
if meta.code.is_none() {
Ok(true)
} else {
Err(Error::new(meta.code.span(), "Attribute #[default] on variants should have no value"))
}
} else {
Ok(false)
}
})?.ok_or_else(|| Error::new(input.span(), "No default variant"))?;
let default_variant_name = &default_variant.ident;
let (body_assignment, doc) = default_body_tt(&default_variant.fields)?;
(quote! {
#name :: #default_variant_name #body_assignment
}, format!("Return `{}::{}{}`", name, default_variant_name, doc))
}
syn::Data::Union(_) => {
panic!()
}
};
Ok(quote! {
impl #impl_generics Default for #name #ty_generics #where_clause {
#[doc = #doc]
fn default() -> Self {
#default_expr
}
}
})
}
fn default_body_tt(body: &syn::Fields) -> Result<(TokenStream, String), Error> {
let mut doc = String::new();
use std::fmt::Write;
let body_tt = match body {
&syn::Fields::Named(ref fields) => {
doc.push_str(" {");
let result = {
let field_assignments = fields.named.iter().map(|field| {
let field_name = field.ident.as_ref();
let (default_value, default_doc) = field_default_expr_and_doc(field)?;
write!(&mut doc, "\n {}: {},", field_name.expect("field value in struct is empty"), default_doc).unwrap();
Ok(quote! { #field_name : #default_value })
}).collect::<Result<Vec<_>, Error>>()?;
quote!{
{
#( #field_assignments ),*
}
}
};
if (&mut doc).ends_with(",") {
doc.pop();
doc.push('\n');
};
doc.push('}');
result
}
&syn::Fields::Unnamed(ref fields) => {
doc.push('(');
let result = {
let field_assignments = fields.unnamed.iter().map(|field| {
let (default_value, default_doc) = field_default_expr_and_doc(field)?;
write!(&mut doc, "{}, ", default_doc).unwrap();
Ok(default_value)
}).collect::<Result<Vec<TokenStream>, Error>>()?;
quote! {
(
#( #field_assignments ),*
)
}
};
if (&mut doc).ends_with(", ") {
doc.pop();
doc.pop();
};
doc.push(')');
result
}
&syn::Fields::Unit => quote!{},
};
Ok((body_tt, doc))
}
fn field_default_expr_and_doc(field: &syn::Field) -> Result<(TokenStream, String), Error> {
if let Some(default_attr) = DefaultAttr::find_in_attributes(&field.attrs)? {
let conversion_strategy = default_attr.conversion_strategy();
let field_value = default_attr.code.ok_or_else(|| {
Error::new(field.span(), "Expected #[default = ...] or #[default(...)]")})?;
let field_value = match conversion_strategy {
ConversionStrategy::NoConversion => field_value,
ConversionStrategy::Into => quote!((#field_value).into()),
};
let field_doc = format!("{}", field_value);
Ok((field_value, field_doc))
} else {
Ok((quote! {
Default::default()
}, "Default::default()".to_owned()))
}
}