Skip to main content

is_default_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro_crate::{FoundCrate, crate_name};
3use quote::quote;
4use syn::{Data, DeriveInput, Fields, parse_macro_input};
5
6/// Derive macro generating an impl of the trait `IsDefault`.
7#[proc_macro_derive(IsDefault, attributes(is_default, default))]
8pub fn derive_is_default(input: TokenStream) -> TokenStream {
9    let input = parse_macro_input!(input as DeriveInput);
10    let name = &input.ident;
11    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
12
13    let crate_path = match crate_name("is_default") {
14        Ok(FoundCrate::Itself) => quote!(::is_default),
15        Ok(FoundCrate::Name(found)) => {
16            let ident = syn::Ident::new(&found, name.span());
17            quote!(::#ident)
18        }
19        Err(_) => quote!(::is_default),
20    };
21
22    let body = match input.data {
23        Data::Struct(data_struct) => match data_struct.fields {
24            Fields::Named(fields_named) => {
25                let checks = fields_named.named.iter().map(|f| {
26                    let name = &f.ident;
27                    quote! {
28                        #crate_path::IsDefault::is_default(&self.#name)
29                    }
30                });
31                quote! {
32                    #(#checks)&&*
33                }
34            }
35            Fields::Unnamed(fields_unnamed) => {
36                let checks = fields_unnamed.unnamed.iter().enumerate().map(|(i, _)| {
37                    let index = syn::Index::from(i);
38                    quote! {
39                        #crate_path::IsDefault::is_default(&self.#index)
40                    }
41                });
42                quote! {
43                    #(#checks)&&*
44                }
45            }
46            Fields::Unit => {
47                quote!(true)
48            }
49        },
50
51        Data::Enum(enum_data) => {
52            let mut default_variant_ident = None;
53
54            for variant in &enum_data.variants {
55                let has_default_attr = variant.attrs.iter().any(|attr| {
56                    let path = attr.path();
57                    path.is_ident("default") || path.is_ident("is_default")
58                });
59
60                if has_default_attr {
61                    if !matches!(variant.fields, Fields::Unit) {
62                        return syn::Error::new_spanned(
63                            &variant.ident,
64                            "the #[default] or #[is_default] attribute may only be used on unit enum variants",
65                        )
66                        .to_compile_error()
67                        .into();
68                    }
69
70                    default_variant_ident = Some(&variant.ident);
71                    break;
72                }
73            }
74
75            match default_variant_ident {
76                Some(ident) => quote!(matches!(self, #name::#ident)),
77                None => quote!(self == &Default::default()),
78            }
79        }
80
81        Data::Union(_) => {
82            return syn::Error::new_spanned(name, "IsDefault cannot be derived for unions")
83                .to_compile_error()
84                .into();
85        }
86    };
87
88    let expanded = quote! {
89        impl #impl_generics #crate_path::IsDefault for #name #ty_generics #where_clause {
90            fn is_default(&self) -> bool {
91                #body
92            }
93        }
94    };
95
96    TokenStream::from(expanded)
97}