#![doc(html_root_url = "http://docs.rs/const-default-derive/0.2.0")]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Literal, Span, TokenStream as TokenStream2};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::{quote, quote_spanned};
use syn::{spanned::Spanned, Error};
#[proc_macro_derive(ConstDefault, attributes(const_default))]
pub fn derive(input: TokenStream) -> TokenStream {
match derive_default(input.into()) {
Ok(output) => output.into(),
Err(error) => error.to_compile_error().into(),
}
}
fn derive_default(input: TokenStream2) -> Result<TokenStream2, syn::Error> {
let crate_ident = query_crate_ident()?;
let input = syn::parse2::<syn::DeriveInput>(input)?;
let ident = input.ident;
let data_struct = match input.data {
syn::Data::Struct(data_struct) => data_struct,
_ => {
return Err(Error::new(
Span::call_site(),
"ConstDefault derive only works on struct types",
))
}
};
let default_impl =
generate_default_impl_struct(&crate_ident, &data_struct)?;
let mut generics = input.generics;
generate_default_impl_where_bounds(
&crate_ident,
&data_struct,
&mut generics,
)?;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
impl #impl_generics #crate_ident::ConstDefault for #ident #ty_generics #where_clause {
const DEFAULT: Self = #default_impl;
}
})
}
fn query_crate_ident() -> Result<TokenStream2, syn::Error> {
let query = crate_name("const-default").map_err(|error| {
Error::new(
Span::call_site(),
format!(
"could not find root crate for ConstDefault derive: {}",
error
),
)
})?;
match query {
FoundCrate::Itself => Ok(quote! { crate }),
FoundCrate::Name(name) => {
let ident = Ident::new(&name, Span::call_site());
Ok(quote! { ::#ident })
}
}
}
fn generate_default_impl_struct(
crate_ident: &TokenStream2,
data_struct: &syn::DataStruct,
) -> Result<TokenStream2, syn::Error> {
let fields_impl =
data_struct.fields.iter().enumerate().map(|(n, field)| {
let field_span = field.span();
let field_type = &field.ty;
let field_pos = Literal::usize_unsuffixed(n);
let field_ident = field
.ident
.as_ref()
.map(|ident| quote_spanned!(field_span=> #ident))
.unwrap_or_else(|| quote_spanned!(field_span=> #field_pos));
quote_spanned!(field_span=>
#field_ident: <#field_type as #crate_ident::ConstDefault>::DEFAULT
)
});
Ok(quote! {
Self {
#( #fields_impl ),*
}
})
}
fn generate_default_impl_where_bounds(
crate_ident: &TokenStream2,
data_struct: &syn::DataStruct,
generics: &mut syn::Generics,
) -> Result<(), syn::Error> {
let where_clause = generics.make_where_clause();
for field in &data_struct.fields {
let field_type = &field.ty;
where_clause.predicates.push(syn::parse_quote!(
#field_type: #crate_ident::ConstDefault
))
}
Ok(())
}