1use std::borrow::Cow;
2
3use doc_comment_from;
4use proc_macro2::{Span, TokenStream};
5use quote::{ToTokens, TokenStreamExt};
6use syn;
7use syn::spanned::Spanned;
8use BuilderPattern;
9use Initializer;
10use DEFAULT_STRUCT_NAME;
11
12use crate::DefaultExpression;
13
14/// Initializer for the struct fields in the build method, implementing
15/// `quote::ToTokens`.
16///
17/// # Examples
18///
19/// Will expand to something like the following (depending on settings):
20///
21/// ```rust,ignore
22/// # extern crate proc_macro2;
23/// # #[macro_use]
24/// # extern crate quote;
25/// # extern crate syn;
26/// # #[macro_use(default_build_method)]
27/// # extern crate derive_builder_core;
28/// # use derive_builder_core::{BuildMethod, BuilderPattern};
29/// # fn main() {
30/// # let build_method = default_build_method!();
31/// #
32/// # assert_eq!(quote!(#build_method).to_string(), quote!(
33/// pub fn build(&self) -> ::derive_builder::export::core::result::Result<Foo, FooBuilderError> {
34/// Ok(Foo {
35/// foo: self.foo,
36/// })
37/// }
38/// # ).to_string());
39/// # }
40/// ```
41#[derive(Debug)]
42pub struct BuildMethod<'a> {
43 /// Path to the root of the derive_builder crate.
44 pub crate_root: &'a syn::Path,
45 /// Enables code generation for this build method.
46 pub enabled: bool,
47 /// Name of this build fn.
48 pub ident: &'a syn::Ident,
49 /// Visibility of the build method, e.g. `syn::Visibility::Public`.
50 pub visibility: Cow<'a, syn::Visibility>,
51 /// How the build method takes and returns `self` (e.g. mutably).
52 pub pattern: BuilderPattern,
53 /// Type of the target field.
54 ///
55 /// The corresonding builder field will be `Option<field_type>`.
56 pub target_ty: &'a syn::Ident,
57 /// Type parameters and lifetimes attached to this builder struct.
58 pub target_ty_generics: Option<syn::TypeGenerics<'a>>,
59 /// Type of error.
60 pub error_ty: syn::Path,
61 /// Field initializers for the target type.
62 pub initializers: Vec<TokenStream>,
63 /// Doc-comment of the builder struct.
64 pub doc_comment: Option<syn::Attribute>,
65 /// Default value for the whole struct.
66 ///
67 /// This will be in scope for all initializers as `__default`.
68 pub default_struct: Option<&'a DefaultExpression>,
69 /// Validation function with signature `&FooBuilder -> Result<(), String>`
70 /// to call before the macro-provided struct buildout.
71 pub validate_fn: Option<&'a syn::Path>,
72}
73
74impl<'a> ToTokens for BuildMethod<'a> {
75 fn to_tokens(&self, tokens: &mut TokenStream) {
76 let ident = &self.ident;
77 let vis = &self.visibility;
78 let target_ty = &self.target_ty;
79 let target_ty_generics = &self.target_ty_generics;
80 let initializers = &self.initializers;
81 let self_param = match self.pattern {
82 BuilderPattern::Owned => quote!(self),
83 BuilderPattern::Mutable | BuilderPattern::Immutable => quote!(&self),
84 };
85 let doc_comment = &self.doc_comment;
86 let default_struct = self.default_struct.as_ref().map(|default_expr| {
87 let default_expr = default_expr.with_crate_root(self.crate_root);
88 let ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site());
89 quote!(let #ident: #target_ty #target_ty_generics = #default_expr;)
90 });
91 let validate_fn = self
92 .validate_fn
93 .as_ref()
94 .map(|vfn| quote_spanned!(vfn.span() => #vfn(&self)?;));
95 let error_ty = &self.error_ty;
96
97 if self.enabled {
98 let crate_root = &self.crate_root;
99 tokens.append_all(quote!(
100 #doc_comment
101 #vis fn #ident(#self_param)
102 -> #crate_root::export::core::result::Result<#target_ty #target_ty_generics, #error_ty>
103 {
104 #validate_fn
105 #default_struct
106 Ok(#target_ty {
107 #(#initializers)*
108 })
109 }
110 ))
111 }
112 }
113}
114
115impl<'a> BuildMethod<'a> {
116 /// Set a doc-comment for this item.
117 pub fn doc_comment(&mut self, s: String) -> &mut Self {
118 self.doc_comment = Some(doc_comment_from(s));
119 self
120 }
121
122 /// Populate the `BuildMethod` with appropriate initializers of the
123 /// underlying struct.
124 ///
125 /// For each struct field this must be called with the appropriate
126 /// initializer.
127 pub fn push_initializer(&mut self, init: Initializer) -> &mut Self {
128 self.initializers.push(quote!(#init));
129 self
130 }
131}
132
133// pub struct BuildMethodError {
134// is_generated: bool,
135// ident: syn::Ident,
136// }
137
138/// Helper macro for unit tests. This is _only_ public in order to be accessible
139/// from doc-tests too.
140#[doc(hidden)]
141#[macro_export]
142macro_rules! default_build_method {
143 () => {
144 BuildMethod {
145 // Deliberately don't use the default value here - make sure
146 // that all test cases are passing crate_root through properly.
147 crate_root: &parse_quote!(::db),
148 enabled: true,
149 ident: &syn::Ident::new("build", ::proc_macro2::Span::call_site()),
150 visibility: ::std::borrow::Cow::Owned(syn::parse_quote!(pub)),
151 pattern: BuilderPattern::Mutable,
152 target_ty: &syn::Ident::new("Foo", ::proc_macro2::Span::call_site()),
153 target_ty_generics: None,
154 error_ty: syn::parse_quote!(FooBuilderError),
155 initializers: vec![quote!(foo: self.foo,)],
156 doc_comment: None,
157 default_struct: None,
158 validate_fn: None,
159 }
160 };
161}
162
163#[cfg(test)]
164mod tests {
165 #[allow(unused_imports)]
166 use super::*;
167
168 #[test]
169 fn std() {
170 let build_method = default_build_method!();
171
172 #[rustfmt::skip]
173 assert_eq!(
174 quote!(#build_method).to_string(),
175 quote!(
176 pub fn build(&self) -> ::db::export::core::result::Result<Foo, FooBuilderError> {
177 Ok(Foo {
178 foo: self.foo,
179 })
180 }
181 )
182 .to_string()
183 );
184 }
185
186 #[test]
187 fn default_struct() {
188 let mut build_method = default_build_method!();
189 let alt_default =
190 DefaultExpression::explicit::<syn::Expr>(parse_quote!(Default::default()));
191 build_method.default_struct = Some(&alt_default);
192
193 #[rustfmt::skip]
194 assert_eq!(
195 quote!(#build_method).to_string(),
196 quote!(
197 pub fn build(&self) -> ::db::export::core::result::Result<Foo, FooBuilderError> {
198 let __default: Foo = { Default::default() };
199 Ok(Foo {
200 foo: self.foo,
201 })
202 }
203 )
204 .to_string()
205 );
206 }
207
208 #[test]
209 fn skip() {
210 let mut build_method = default_build_method!();
211 build_method.enabled = false;
212 build_method.enabled = false;
213
214 assert_eq!(quote!(#build_method).to_string(), quote!().to_string());
215 }
216
217 #[test]
218 fn rename() {
219 let ident = syn::Ident::new("finish", Span::call_site());
220 let mut build_method: BuildMethod = default_build_method!();
221 build_method.ident = &ident;
222
223 #[rustfmt::skip]
224 assert_eq!(
225 quote!(#build_method).to_string(),
226 quote!(
227 pub fn finish(&self) -> ::db::export::core::result::Result<Foo, FooBuilderError> {
228 Ok(Foo {
229 foo: self.foo,
230 })
231 }
232 )
233 .to_string()
234 );
235 }
236
237 #[test]
238 fn validation() {
239 let validate_path: syn::Path = parse_quote!(IpsumBuilder::validate);
240
241 let mut build_method: BuildMethod = default_build_method!();
242 build_method.validate_fn = Some(&validate_path);
243
244 #[rustfmt::skip]
245 assert_eq!(
246 quote!(#build_method).to_string(),
247 quote!(
248 pub fn build(&self) -> ::db::export::core::result::Result<Foo, FooBuilderError> {
249 IpsumBuilder::validate(&self)?;
250
251 Ok(Foo {
252 foo: self.foo,
253 })
254 }
255 )
256 .to_string()
257 );
258 }
259}
260