variant_builder_macro/
lib.rs1use 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 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 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 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 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 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 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 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 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 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}