use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::parse_macro_input;
use syn::{Attribute, Data, DeriveInput, Expr, Fields, Ident, Type, Visibility};
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub(crate) enum FieldDefault {
Required,
DefaultTrait,
DefaultExpr(Expr),
}
pub(crate) struct BField {
pub ident: Ident,
pub ty: Type,
pub default: FieldDefault,
}
pub(crate) enum BuildKind {
Bitfield,
Plain,
}
pub(crate) fn parse_builder_attr(attr: &Attribute) -> syn::Result<Option<FieldDefault>> {
if !attr.path().is_ident("builder") {
return Ok(None);
}
let mut result = FieldDefault::DefaultTrait;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
if meta.input.peek(syn::Token![=]) {
let expr: Expr = meta.value()?.parse()?;
result = FieldDefault::DefaultExpr(expr);
} else {
result = FieldDefault::DefaultTrait;
}
Ok(())
} else {
Err(meta.error("expected `default` or `default = <expr>`"))
}
})?;
Ok(Some(result))
}
pub(crate) fn generate(
name: &Ident,
vis: &Visibility,
fields: &[BField],
kind: BuildKind,
post_build: Option<&TokenStream2>,
) -> TokenStream2 {
let bnb = crate::bnb_path();
let builder_name = format_ident!("{}Builder", name);
let idents: Vec<&Ident> = fields.iter().map(|f| &f.ident).collect();
let tys: Vec<&Type> = fields.iter().map(|f| &f.ty).collect();
let resolve = fields.iter().map(|f| {
let id = &f.ident;
match &f.default {
FieldDefault::Required => quote! {
let #id = self.#id.ok_or_else(
|| #bnb::BuilderError::missing_field(stringify!(#id)),
)?;
},
FieldDefault::DefaultTrait => quote!(let #id = self.#id.unwrap_or_default();),
FieldDefault::DefaultExpr(e) => quote!(let #id = self.#id.unwrap_or_else(|| #e);),
}
});
let construct = match kind {
BuildKind::Bitfield => {
let withs = fields.iter().map(|f| {
let id = &f.ident;
let with = format_ident!("with_{}", id);
quote!(.#with(#id))
});
quote!(#name::new() #(#withs)*)
}
BuildKind::Plain => quote!(#name { #( #idents ),* }),
};
quote! {
impl #name {
#[doc = concat!("Returns a builder for [`", stringify!(#name), "`].")]
#vis fn builder() -> #builder_name {
#builder_name::default()
}
}
#[doc = concat!(
"Builder for [`", stringify!(#name),
"`]; `build` errors on any unset required field."
)]
#[derive(Default)]
#vis struct #builder_name {
#( #idents: ::core::option::Option<#tys>, )*
}
impl #builder_name {
#(
#[doc = concat!("Sets `", stringify!(#idents), "`.")]
#vis fn #idents(mut self, value: #tys) -> Self {
self.#idents = ::core::option::Option::Some(value);
self
}
)*
#vis fn build(self) -> ::core::result::Result<#name, #bnb::BuilderError> {
#(#resolve)*
let __value = #construct;
#post_build
::core::result::Result::Ok(__value)
}
}
}
}
pub(crate) fn expand_derive(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
match expand_derive_inner(input) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn expand_derive_inner(input: DeriveInput) -> syn::Result<TokenStream2> {
let named = match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(n) => n,
_ => {
return Err(syn::Error::new_spanned(
&input.ident,
"BitsBuilder requires a struct with named fields",
));
}
},
_ => {
return Err(syn::Error::new_spanned(
&input.ident,
"BitsBuilder can only derive for structs",
));
}
};
let mut fields = Vec::new();
for f in &named.named {
let ident = f.ident.clone().expect("named field");
let ty = f.ty.clone();
let mut default = FieldDefault::Required;
for attr in &f.attrs {
if let Some(d) = parse_builder_attr(attr)? {
default = d;
}
}
fields.push(BField { ident, ty, default });
}
Ok(generate(
&input.ident,
&input.vis,
&fields,
BuildKind::Plain,
None,
))
}