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

Provided by KDAB

Privacy Policy
Learn Rust with the experts
Find out more