1use proc_macro::TokenStream;
2use proc_macro_crate::{FoundCrate, crate_name};
3use quote::quote;
4use syn::{Data, DeriveInput, Fields, parse_macro_input};
5
6#[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}