variant_builder_macro/
lib.rs

1use convert_case::{Case, Casing};
2use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use syn::{parse_macro_input, Data, DeriveInput, Fields};
5
6#[proc_macro_derive(VariantBuilder, attributes(default, no_builder, nested_builder))]
7pub fn variant_builder_macro(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
8    use tracing::trace;
9
10    // ──────────────────────────────────────────────────────────────
11    // Parse and validate input
12    // ──────────────────────────────────────────────────────────────
13    let input     = syn::parse_macro_input!(input as syn::DeriveInput);
14    let enum_ident = input.ident.clone();
15
16    trace!("Generating VariantBuilder impl for enum `{}`", enum_ident);
17
18    let data = match input.data {
19        syn::Data::Enum(d) => d,
20        _ => panic!("#[derive(VariantBuilder)] can only be applied to enums"),
21    };
22
23    // ──────────────────────────────────────────────────────────────
24    // Build helper methods
25    // ──────────────────────────────────────────────────────────────
26    let mut default_variant: Option<syn::Ident> = None;
27    let mut helper_fns = Vec::<proc_macro2::TokenStream>::new();
28
29    for variant in &data.variants {
30        let variant_ident = &variant.ident;
31
32        // Every variant must be a 1‑tuple variant.
33        let field_ty = match &variant.fields {
34            syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
35                &fields.unnamed.first().unwrap().ty
36            }
37            _ => panic!(
38                "Variant `{}` must be a tuple variant with exactly one unnamed field.",
39                variant_ident
40            ),
41        };
42
43        // detect #[default]
44        if variant.attrs.iter().any(|a| a.path().is_ident("default")) {
45            if default_variant.is_some() {
46                panic!("Only one variant in `{}` can be marked #[default]", enum_ident);
47            }
48            default_variant = Some(variant_ident.clone());
49        }
50
51        let has_no_builder = variant
52            .attrs
53            .iter()
54            .any(|a| a.path().is_ident("no_builder"));
55
56        let has_nested_builder = variant
57            .attrs
58            .iter()
59            .any(|a| a.path().is_ident("nested_builder"));
60
61        if has_no_builder && has_nested_builder {
62            panic!(
63                "Variant `{}` cannot use both #[no_builder] and #[nested_builder]",
64                variant_ident
65            );
66        }
67
68        let helper_fn_ident = quote::format_ident!(
69            "{}",
70            variant_ident.to_string().to_case(convert_case::Case::Snake)
71        );
72
73        trace!(
74            variant = %variant_ident,
75            helper  = %helper_fn_ident,
76            no_builder = has_no_builder,
77            nested_builder = has_nested_builder,
78            "emitting helper"
79        );
80
81        if has_no_builder {
82            // direct‑value mode
83            helper_fns.push(quote::quote! {
84                pub fn #helper_fn_ident(inner: #field_ty) -> Self {
85                    Self::#variant_ident(inner)
86                }
87            });
88        } else if has_nested_builder {
89            // nested‑builder mode
90            helper_fns.push(quote::quote! {
91                pub fn #helper_fn_ident<F>(build: F) -> Self
92                where
93                    F: FnOnce() -> #field_ty,
94                {
95                    Self::#variant_ident(build())
96                }
97            });
98        } else {
99            // legacy builder‑delegation mode
100            let builder_ty_str = format!("{}Builder", quote::quote!(#field_ty));
101            let builder_ty: syn::Type =
102                syn::parse_str(&builder_ty_str).expect("valid builder type path");
103
104            helper_fns.push(quote::quote! {
105                pub fn #helper_fn_ident<F>(build: F) -> Self
106                where
107                    F: FnOnce(&mut #builder_ty),
108                {
109                    let mut builder = #builder_ty::default();
110                    build(&mut builder);
111                    Self::#variant_ident(
112                        builder.build().expect("Builder failed to construct inner value")
113                    )
114                }
115            });
116        }
117    }
118
119    // ──────────────────────────────────────────────────────────────
120    // Optional Default impl
121    // ──────────────────────────────────────────────────────────────
122    let default_impl = if let Some(def_ident) = default_variant {
123        quote::quote! {
124            impl ::core::default::Default for #enum_ident {
125                fn default() -> Self {
126                    Self::#def_ident(::core::default::Default::default())
127                }
128            }
129        }
130    } else {
131        quote::quote! {}
132    };
133
134    // ──────────────────────────────────────────────────────────────
135    // Generate final token stream
136    // ──────────────────────────────────────────────────────────────
137    let expanded = quote::quote! {
138        impl #enum_ident {
139            #(#helper_fns)*
140        }
141
142        #default_impl
143    };
144
145    ::proc_macro::TokenStream::from(expanded)
146}