blob: af2fc3161c523b6191df05d7d07a56fbbb4806e8 [file] [log] [blame]
use doc_comment_from;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use syn;
use syn::spanned::Spanned;
use BuilderPattern;
use Initializer;
use DEFAULT_STRUCT_NAME;
use crate::DefaultExpression;
/// Initializer for the struct fields in the build method, implementing
/// `quote::ToTokens`.
///
/// # Examples
///
/// Will expand to something like the following (depending on settings):
///
/// ```rust,ignore
/// # extern crate proc_macro2;
/// # #[macro_use]
/// # extern crate quote;
/// # extern crate syn;
/// # #[macro_use(default_build_method)]
/// # extern crate derive_builder_core;
/// # use derive_builder_core::{BuildMethod, BuilderPattern};
/// # fn main() {
/// # let build_method = default_build_method!();
/// #
/// # assert_eq!(quote!(#build_method).to_string(), quote!(
/// pub fn build(&self) -> ::derive_builder::export::core::result::Result<Foo, FooBuilderError> {
/// Ok(Foo {
/// foo: self.foo,
/// })
/// }
/// # ).to_string());
/// # }
/// ```
#[derive(Debug)]
pub struct BuildMethod<'a> {
/// Enables code generation for this build method.
pub enabled: bool,
/// Name of this build fn.
pub ident: &'a syn::Ident,
/// Visibility of the build method, e.g. `syn::Visibility::Public`.
pub visibility: syn::Visibility,
/// How the build method takes and returns `self` (e.g. mutably).
pub pattern: BuilderPattern,
/// Type of the target field.
///
/// The corresonding builder field will be `Option<field_type>`.
pub target_ty: &'a syn::Ident,
/// Type parameters and lifetimes attached to this builder struct.
pub target_ty_generics: Option<syn::TypeGenerics<'a>>,
/// Type of error.
pub error_ty: syn::Path,
/// Field initializers for the target type.
pub initializers: Vec<TokenStream>,
/// Doc-comment of the builder struct.
pub doc_comment: Option<syn::Attribute>,
/// Default value for the whole struct.
///
/// This will be in scope for all initializers as `__default`.
pub default_struct: Option<&'a DefaultExpression>,
/// Validation function with signature `&FooBuilder -> Result<(), String>`
/// to call before the macro-provided struct buildout.
pub validate_fn: Option<&'a syn::Path>,
}
impl<'a> ToTokens for BuildMethod<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ident = &self.ident;
let vis = &self.visibility;
let target_ty = &self.target_ty;
let target_ty_generics = &self.target_ty_generics;
let initializers = &self.initializers;
let self_param = match self.pattern {
BuilderPattern::Owned => quote!(self),
BuilderPattern::Mutable | BuilderPattern::Immutable => quote!(&self),
};
let doc_comment = &self.doc_comment;
let default_struct = self.default_struct.as_ref().map(|default_expr| {
let ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site());
quote!(let #ident: #target_ty #target_ty_generics = #default_expr;)
});
let validate_fn = self
.validate_fn
.as_ref()
.map(|vfn| quote_spanned!(vfn.span() => #vfn(&self)?;));
let error_ty = &self.error_ty;
if self.enabled {
tokens.append_all(quote!(
#doc_comment
#vis fn #ident(#self_param)
-> ::derive_builder::export::core::result::Result<#target_ty #target_ty_generics, #error_ty>
{
#validate_fn
#default_struct
Ok(#target_ty {
#(#initializers)*
})
}
))
}
}
}
impl<'a> BuildMethod<'a> {
/// Set a doc-comment for this item.
pub fn doc_comment(&mut self, s: String) -> &mut Self {
self.doc_comment = Some(doc_comment_from(s));
self
}
/// Populate the `BuildMethod` with appropriate initializers of the
/// underlying struct.
///
/// For each struct field this must be called with the appropriate
/// initializer.
pub fn push_initializer(&mut self, init: Initializer) -> &mut Self {
self.initializers.push(quote!(#init));
self
}
}
// pub struct BuildMethodError {
// is_generated: bool,
// ident: syn::Ident,
// }
/// Helper macro for unit tests. This is _only_ public in order to be accessible
/// from doc-tests too.
#[doc(hidden)]
#[macro_export]
macro_rules! default_build_method {
() => {
BuildMethod {
enabled: true,
ident: &syn::Ident::new("build", ::proc_macro2::Span::call_site()),
visibility: syn::parse_quote!(pub),
pattern: BuilderPattern::Mutable,
target_ty: &syn::Ident::new("Foo", ::proc_macro2::Span::call_site()),
target_ty_generics: None,
error_ty: syn::parse_quote!(FooBuilderError),
initializers: vec![quote!(foo: self.foo,)],
doc_comment: None,
default_struct: None,
validate_fn: None,
}
};
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn std() {
let build_method = default_build_method!();
#[rustfmt::skip]
assert_eq!(
quote!(#build_method).to_string(),
quote!(
pub fn build(&self) -> ::derive_builder::export::core::result::Result<Foo, FooBuilderError> {
Ok(Foo {
foo: self.foo,
})
}
)
.to_string()
);
}
#[test]
fn default_struct() {
let mut build_method = default_build_method!();
let alt_default =
DefaultExpression::explicit::<syn::Expr>(parse_quote!(Default::default()));
build_method.default_struct = Some(&alt_default);
#[rustfmt::skip]
assert_eq!(
quote!(#build_method).to_string(),
quote!(
pub fn build(&self) -> ::derive_builder::export::core::result::Result<Foo, FooBuilderError> {
let __default: Foo = { Default::default() };
Ok(Foo {
foo: self.foo,
})
}
)
.to_string()
);
}
#[test]
fn skip() {
let mut build_method = default_build_method!();
build_method.enabled = false;
build_method.enabled = false;
assert_eq!(quote!(#build_method).to_string(), quote!().to_string());
}
#[test]
fn rename() {
let ident = syn::Ident::new("finish", Span::call_site());
let mut build_method: BuildMethod = default_build_method!();
build_method.ident = &ident;
#[rustfmt::skip]
assert_eq!(
quote!(#build_method).to_string(),
quote!(
pub fn finish(&self) -> ::derive_builder::export::core::result::Result<Foo, FooBuilderError> {
Ok(Foo {
foo: self.foo,
})
}
)
.to_string()
);
}
#[test]
fn validation() {
let validate_path: syn::Path = parse_quote!(IpsumBuilder::validate);
let mut build_method: BuildMethod = default_build_method!();
build_method.validate_fn = Some(&validate_path);
#[rustfmt::skip]
assert_eq!(
quote!(#build_method).to_string(),
quote!(
pub fn build(&self) -> ::derive_builder::export::core::result::Result<Foo, FooBuilderError> {
IpsumBuilder::validate(&self)?;
Ok(Foo {
foo: self.foo,
})
}
)
.to_string()
);
}
}