1use std::borrow::Cow;
2
3use proc_macro2::TokenStream;
4use quote::{format_ident, ToTokens, TokenStreamExt};
5use syn::punctuated::Punctuated;
6use syn::{self, Path, TraitBound, TraitBoundModifier, TypeParamBound};
7
8use doc_comment_from;
9use BuildMethod;
10use BuilderField;
11use BuilderPattern;
12use DeprecationNotes;
13use Setter;
14
15/// Builder, implementing `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]
27/// # extern crate derive_builder_core;
28/// # use quote::TokenStreamExt;
29/// # use derive_builder_core::{Builder, DeprecationNotes};
30/// # fn main() {
31/// # let builder = default_builder!();
32/// #
33/// # assert_eq!(
34/// # quote!(#builder).to_string(),
35/// # {
36/// # let mut result = quote!();
37/// # #[cfg(not(feature = "clippy"))]
38/// # result.append_all(quote!(#[allow(clippy::all)]));
39/// #
40/// # result.append_all(quote!(
41/// #[derive(Clone)]
42/// pub struct FooBuilder {
43/// foo: u32,
44/// }
45///
46/// #[doc="Error type for FooBuilder"]
47/// #[derive(Debug)]
48/// #[non_exhaustive]
49/// pub enum FooBuilderError {
50/// /// Uninitialized field
51/// UninitializedField(&'static str),
52/// /// Custom validation error
53/// ValidationError(::derive_builder::export::core::string::String),
54/// }
55///
56/// impl ::derive_builder::export::core::convert::From<... various ...> for FooBuilderError {}
57///
58/// #[cfg(not(no_std))]
59/// impl std::error::Error for FooBuilderError {}
60/// # ));
61/// # #[cfg(not(feature = "clippy"))]
62/// # result.append_all(quote!(#[allow(clippy::all)]));
63/// #
64/// # result.append_all(quote!(
65///
66/// #[allow(dead_code)]
67/// impl FooBuilder {
68/// fn bar () -> {
69/// unimplemented!()
70/// }
71/// }
72///
73/// impl ::derive_builder::export::core::default::Default for FooBuilder {
74/// fn default() -> Self {
75/// Self {
76/// foo: ::derive_builder::export::core::default::Default::default(),
77/// }
78/// }
79/// }
80///
81/// # ));
82/// # result
83/// # }.to_string()
84/// # );
85/// # }
86/// ```
87#[derive(Debug)]
88pub struct Builder<'a> {
89 /// Path to the root of the derive_builder crate.
90 pub crate_root: &'a Path,
91 /// Enables code generation for this builder struct.
92 pub enabled: bool,
93 /// Name of this builder struct.
94 pub ident: syn::Ident,
95 /// Pattern of this builder struct.
96 pub pattern: BuilderPattern,
97 /// Traits to automatically derive on the builder type.
98 pub derives: &'a [Path],
99 /// Attributes to include on the builder `struct` declaration.
100 pub struct_attrs: &'a [syn::Attribute],
101 /// Attributes to include on the builder's inherent `impl` block.
102 pub impl_attrs: &'a [syn::Attribute],
103 /// When true, generate `impl Default for #ident` which calls the `create_empty` inherent method.
104 ///
105 /// Note that the name of `create_empty` can be overridden; see the `create_empty` field for more.
106 pub impl_default: bool,
107 /// The identifier of the inherent method that creates a builder with all fields set to
108 /// `None` or `PhantomData`.
109 ///
110 /// This method will be invoked by `impl Default` for the builder, but it is also accessible
111 /// to `impl` blocks on the builder that expose custom constructors.
112 pub create_empty: syn::Ident,
113 /// Type parameters and lifetimes attached to this builder's struct
114 /// definition.
115 pub generics: Option<&'a syn::Generics>,
116 /// Visibility of the builder struct, e.g. `syn::Visibility::Public`.
117 pub visibility: Cow<'a, syn::Visibility>,
118 /// Fields of the builder struct, e.g. `foo: u32,`
119 ///
120 /// Expects each entry to be terminated by a comma.
121 pub fields: Vec<TokenStream>,
122 /// Builder field initializers, e.g. `foo: Default::default(),`
123 ///
124 /// Expects each entry to be terminated by a comma.
125 pub field_initializers: Vec<TokenStream>,
126 /// Functions of the builder struct, e.g. `fn bar() -> { unimplemented!() }`
127 pub functions: Vec<TokenStream>,
128 /// Whether or not a generated error type is required.
129 ///
130 /// This would be `false` in the case where an already-existing error is to be used.
131 pub generate_error: bool,
132 /// Whether this builder must derive `Clone`.
133 ///
134 /// This is true even for a builder using the `owned` pattern if there is a field whose setter
135 /// uses a different pattern.
136 pub must_derive_clone: bool,
137 /// Doc-comment of the builder struct.
138 pub doc_comment: Option<syn::Attribute>,
139 /// Emit deprecation notes to the user.
140 pub deprecation_notes: DeprecationNotes,
141 /// Whether or not a libstd is used.
142 pub std: bool,
143}
144
145impl<'a> ToTokens for Builder<'a> {
146 fn to_tokens(&self, tokens: &mut TokenStream) {
147 if self.enabled {
148 let crate_root = self.crate_root;
149 let builder_vis = &self.visibility;
150 let builder_ident = &self.ident;
151 let bounded_generics = self.compute_impl_bounds();
152 let (impl_generics, _, _) = bounded_generics.split_for_impl();
153 let (struct_generics, ty_generics, where_clause) = self
154 .generics
155 .map(syn::Generics::split_for_impl)
156 .map(|(i, t, w)| (Some(i), Some(t), Some(w)))
157 .unwrap_or((None, None, None));
158 let builder_fields = &self.fields;
159 let builder_field_initializers = &self.field_initializers;
160 let create_empty = &self.create_empty;
161 let functions = &self.functions;
162
163 // Create the comma-separated set of derived traits for the builder
164 let derive_attr = {
165 let clone_trait: Path = parse_quote!(Clone);
166
167 let mut traits: Punctuated<&Path, Token![,]> = Default::default();
168 if self.must_derive_clone {
169 traits.push(&clone_trait);
170 }
171 traits.extend(self.derives);
172
173 if traits.is_empty() {
174 quote!()
175 } else {
176 quote!(#[derive(#traits)])
177 }
178 };
179
180 let struct_attrs = self.struct_attrs;
181 let impl_attrs = self.impl_attrs;
182
183 let builder_doc_comment = &self.doc_comment;
184 let deprecation_notes = &self.deprecation_notes.as_item();
185
186 #[cfg(not(feature = "clippy"))]
187 tokens.append_all(quote!(#[allow(clippy::all)]));
188
189 // struct_attrs MUST come after derive_attr, otherwise attributes for a derived
190 // trait will appear before its derivation. As of rustc 1.59.0 this is a compiler
191 // warning; see https://github.com/rust-lang/rust/issues/79202
192 tokens.append_all(quote!(
193 #derive_attr
194 #(#struct_attrs)*
195 #builder_doc_comment
196 #builder_vis struct #builder_ident #struct_generics #where_clause {
197 #(#builder_fields)*
198 }
199 ));
200
201 #[cfg(not(feature = "clippy"))]
202 tokens.append_all(quote!(#[allow(clippy::all)]));
203
204 tokens.append_all(quote!(
205 #(#impl_attrs)*
206 #[allow(dead_code)]
207 impl #impl_generics #builder_ident #ty_generics #where_clause {
208 #(#functions)*
209 #deprecation_notes
210
211 /// Create an empty builder, with all fields set to `None` or `PhantomData`.
212 fn #create_empty() -> Self {
213 Self {
214 #(#builder_field_initializers)*
215 }
216 }
217 }
218 ));
219
220 if self.impl_default {
221 tokens.append_all(quote!(
222 impl #impl_generics #crate_root::export::core::default::Default for #builder_ident #ty_generics #where_clause {
223 fn default() -> Self {
224 Self::#create_empty()
225 }
226 }
227 ));
228 }
229
230 if self.generate_error {
231 let builder_error_ident = format_ident!("{}Error", builder_ident);
232 let builder_error_doc = format!("Error type for {}", builder_ident);
233
234 tokens.append_all(quote!(
235 #[doc=#builder_error_doc]
236 #[derive(Debug)]
237 #[non_exhaustive]
238 #builder_vis enum #builder_error_ident {
239 /// Uninitialized field
240 UninitializedField(&'static str),
241 /// Custom validation error
242 ValidationError(#crate_root::export::core::string::String),
243 }
244
245 impl #crate_root::export::core::convert::From<#crate_root::UninitializedFieldError> for #builder_error_ident {
246 fn from(s: #crate_root::UninitializedFieldError) -> Self {
247 Self::UninitializedField(s.field_name())
248 }
249 }
250
251 impl #crate_root::export::core::convert::From<#crate_root::export::core::string::String> for #builder_error_ident {
252 fn from(s: #crate_root::export::core::string::String) -> Self {
253 Self::ValidationError(s)
254 }
255 }
256
257 impl #crate_root::export::core::fmt::Display for #builder_error_ident {
258 fn fmt(&self, f: &mut #crate_root::export::core::fmt::Formatter) -> #crate_root::export::core::fmt::Result {
259 match self {
260 Self::UninitializedField(ref field) => write!(f, "`{}` must be initialized", field),
261 Self::ValidationError(ref error) => write!(f, "{}", error),
262 }
263 }
264 }
265 ));
266
267 if self.std {
268 tokens.append_all(quote!(
269 impl std::error::Error for #builder_error_ident {}
270 ));
271 }
272 }
273 }
274 }
275}
276
277impl<'a> Builder<'a> {
278 /// Set a doc-comment for this item.
279 pub fn doc_comment(&mut self, s: String) -> &mut Self {
280 self.doc_comment = Some(doc_comment_from(s));
281 self
282 }
283
284 /// Add a field to the builder
285 pub fn push_field(&mut self, f: BuilderField) -> &mut Self {
286 self.fields.push(quote!(#f));
287 self.field_initializers.push(f.default_initializer_tokens());
288 self
289 }
290
291 /// Add a setter function to the builder
292 pub fn push_setter_fn(&mut self, f: Setter) -> &mut Self {
293 self.functions.push(quote!(#f));
294 self
295 }
296
297 /// Add final build function to the builder
298 pub fn push_build_fn(&mut self, f: BuildMethod) -> &mut Self {
299 self.functions.push(quote!(#f));
300 self
301 }
302
303 /// Add `Clone` trait bound to generic types for non-owned builders.
304 /// This enables target types to declare generics without requiring a
305 /// `Clone` impl. This is the same as how the built-in derives for
306 /// `Clone`, `Default`, `PartialEq`, and other traits work.
307 fn compute_impl_bounds(&self) -> syn::Generics {
308 if let Some(type_gen) = self.generics {
309 let mut generics = type_gen.clone();
310
311 if !self.pattern.requires_clone() || type_gen.type_params().next().is_none() {
312 return generics;
313 }
314
315 let crate_root = self.crate_root;
316
317 let clone_bound = TypeParamBound::Trait(TraitBound {
318 paren_token: None,
319 modifier: TraitBoundModifier::None,
320 lifetimes: None,
321 path: syn::parse_quote!(#crate_root::export::core::clone::Clone),
322 });
323
324 for typ in generics.type_params_mut() {
325 typ.bounds.push(clone_bound.clone());
326 }
327
328 generics
329 } else {
330 Default::default()
331 }
332 }
333}
334
335/// Helper macro for unit tests. This is _only_ public in order to be accessible
336/// from doc-tests too.
337#[doc(hidden)]
338#[macro_export]
339macro_rules! default_builder {
340 () => {
341 Builder {
342 // Deliberately don't use the default value here - make sure
343 // that all test cases are passing crate_root through properly.
344 crate_root: &parse_quote!(::db),
345 enabled: true,
346 ident: syn::Ident::new("FooBuilder", ::proc_macro2::Span::call_site()),
347 pattern: Default::default(),
348 derives: &vec![],
349 struct_attrs: &vec![],
350 impl_attrs: &vec![],
351 impl_default: true,
352 create_empty: syn::Ident::new("create_empty", ::proc_macro2::Span::call_site()),
353 generics: None,
354 visibility: ::std::borrow::Cow::Owned(parse_quote!(pub)),
355 fields: vec![quote!(foo: u32,)],
356 field_initializers: vec![quote!(foo: ::db::export::core::default::Default::default(), )],
357 functions: vec![quote!(fn bar() -> { unimplemented!() })],
358 generate_error: true,
359 must_derive_clone: true,
360 doc_comment: None,
361 deprecation_notes: DeprecationNotes::default(),
362 std: true,
363 }
364 };
365}
366
367#[cfg(test)]
368mod tests {
369 #[allow(unused_imports)]
370 use super::*;
371 use proc_macro2::TokenStream;
372 use syn::Ident;
373
374 fn add_generated_error(result: &mut TokenStream) {
375 result.append_all(quote!(
376 #[doc="Error type for FooBuilder"]
377 #[derive(Debug)]
378 #[non_exhaustive]
379 pub enum FooBuilderError {
380 /// Uninitialized field
381 UninitializedField(&'static str),
382 /// Custom validation error
383 ValidationError(::db::export::core::string::String),
384 }
385
386 impl ::db::export::core::convert::From<::db::UninitializedFieldError> for FooBuilderError {
387 fn from(s: ::db::UninitializedFieldError) -> Self {
388 Self::UninitializedField(s.field_name())
389 }
390 }
391
392 impl ::db::export::core::convert::From<::db::export::core::string::String> for FooBuilderError {
393 fn from(s: ::db::export::core::string::String) -> Self {
394 Self::ValidationError(s)
395 }
396 }
397
398 impl ::db::export::core::fmt::Display for FooBuilderError {
399 fn fmt(&self, f: &mut ::db::export::core::fmt::Formatter) -> ::db::export::core::fmt::Result {
400 match self {
401 Self::UninitializedField(ref field) => write!(f, "`{}` must be initialized", field),
402 Self::ValidationError(ref error) => write!(f, "{}", error),
403 }
404 }
405 }
406
407 impl std::error::Error for FooBuilderError {}
408 ));
409 }
410
411 #[test]
412 fn simple() {
413 let builder = default_builder!();
414
415 assert_eq!(
416 quote!(#builder).to_string(),
417 {
418 let mut result = quote!();
419
420 #[cfg(not(feature = "clippy"))]
421 result.append_all(quote!(#[allow(clippy::all)]));
422
423 result.append_all(quote!(
424 #[derive(Clone)]
425 pub struct FooBuilder {
426 foo: u32,
427 }
428 ));
429
430 #[cfg(not(feature = "clippy"))]
431 result.append_all(quote!(#[allow(clippy::all)]));
432
433 result.append_all(quote!(
434 #[allow(dead_code)]
435 impl FooBuilder {
436 fn bar () -> {
437 unimplemented!()
438 }
439
440 /// Create an empty builder, with all fields set to `None` or `PhantomData`.
441 fn create_empty() -> Self {
442 Self {
443 foo: ::db::export::core::default::Default::default(),
444 }
445 }
446 }
447
448 impl ::db::export::core::default::Default for FooBuilder {
449 fn default() -> Self {
450 Self::create_empty()
451 }
452 }
453 ));
454
455 add_generated_error(&mut result);
456
457 result
458 }
459 .to_string()
460 );
461 }
462
463 #[test]
464 fn rename_create_empty() {
465 let mut builder = default_builder!();
466 builder.create_empty = Ident::new("empty", proc_macro2::Span::call_site());
467
468 assert_eq!(
469 quote!(#builder).to_string(),
470 {
471 let mut result = quote!();
472
473 #[cfg(not(feature = "clippy"))]
474 result.append_all(quote!(#[allow(clippy::all)]));
475
476 result.append_all(quote!(
477 #[derive(Clone)]
478 pub struct FooBuilder {
479 foo: u32,
480 }
481 ));
482
483 #[cfg(not(feature = "clippy"))]
484 result.append_all(quote!(#[allow(clippy::all)]));
485
486 result.append_all(quote!(
487 #[allow(dead_code)]
488 impl FooBuilder {
489 fn bar () -> {
490 unimplemented!()
491 }
492
493 /// Create an empty builder, with all fields set to `None` or `PhantomData`.
494 fn empty() -> Self {
495 Self {
496 foo: ::db::export::core::default::Default::default(),
497 }
498 }
499 }
500
501 impl ::db::export::core::default::Default for FooBuilder {
502 fn default() -> Self {
503 Self::empty()
504 }
505 }
506 ));
507
508 add_generated_error(&mut result);
509
510 result
511 }
512 .to_string()
513 );
514 }
515
516 // This test depends on the exact formatting of the `stringify`'d code,
517 // so we don't automatically format the test
518 #[rustfmt::skip]
519 #[test]
520 fn generic() {
521 let ast: syn::DeriveInput = parse_quote! {
522 struct Lorem<'a, T: Debug> where T: PartialEq { }
523 };
524 let generics = ast.generics;
525 let mut builder = default_builder!();
526 builder.generics = Some(&generics);
527
528 assert_eq!(
529 quote!(#builder).to_string(),
530 {
531 let mut result = quote!();
532
533 #[cfg(not(feature = "clippy"))]
534 result.append_all(quote!(#[allow(clippy::all)]));
535
536 result.append_all(quote!(
537 #[derive(Clone)]
538 pub struct FooBuilder<'a, T: Debug> where T: PartialEq {
539 foo: u32,
540 }
541 ));
542
543 #[cfg(not(feature = "clippy"))]
544 result.append_all(quote!(#[allow(clippy::all)]));
545
546 result.append_all(quote!(
547 #[allow(dead_code)]
548 impl<'a, T: Debug + ::db::export::core::clone::Clone> FooBuilder<'a, T> where T: PartialEq {
549 fn bar() -> {
550 unimplemented!()
551 }
552
553 /// Create an empty builder, with all fields set to `None` or `PhantomData`.
554 fn create_empty() -> Self {
555 Self {
556 foo: ::db::export::core::default::Default::default(),
557 }
558 }
559 }
560
561 impl<'a, T: Debug + ::db::export::core::clone::Clone> ::db::export::core::default::Default for FooBuilder<'a, T> where T: PartialEq {
562 fn default() -> Self {
563 Self::create_empty()
564 }
565 }
566 ));
567
568 add_generated_error(&mut result);
569
570 result
571 }.to_string()
572 );
573 }
574
575 // This test depends on the exact formatting of the `stringify`'d code,
576 // so we don't automatically format the test
577 #[rustfmt::skip]
578 #[test]
579 fn generic_reference() {
580 let ast: syn::DeriveInput = parse_quote! {
581 struct Lorem<'a, T: 'a + Default> where T: PartialEq{ }
582 };
583
584 let generics = ast.generics;
585 let mut builder = default_builder!();
586 builder.generics = Some(&generics);
587
588 assert_eq!(
589 quote!(#builder).to_string(),
590 {
591 let mut result = quote!();
592
593 #[cfg(not(feature = "clippy"))]
594 result.append_all(quote!(#[allow(clippy::all)]));
595
596 result.append_all(quote!(
597 #[derive(Clone)]
598 pub struct FooBuilder<'a, T: 'a + Default> where T: PartialEq {
599 foo: u32,
600 }
601 ));
602
603 #[cfg(not(feature = "clippy"))]
604 result.append_all(quote!(#[allow(clippy::all)]));
605
606 result.append_all(quote!(
607 #[allow(dead_code)]
608 impl<'a, T: 'a + Default + ::db::export::core::clone::Clone> FooBuilder<'a, T>
609 where
610 T: PartialEq
611 {
612 fn bar() -> {
613 unimplemented!()
614 }
615
616 /// Create an empty builder, with all fields set to `None` or `PhantomData`.
617 fn create_empty() -> Self {
618 Self {
619 foo: ::db::export::core::default::Default::default(),
620 }
621 }
622 }
623
624 impl<'a, T: 'a + Default + ::db::export::core::clone::Clone> ::db::export::core::default::Default for FooBuilder<'a, T> where T: PartialEq {
625 fn default() -> Self {
626 Self::create_empty()
627 }
628 }
629 ));
630
631 add_generated_error(&mut result);
632
633 result
634 }.to_string()
635 );
636 }
637
638 // This test depends on the exact formatting of the `stringify`'d code,
639 // so we don't automatically format the test
640 #[rustfmt::skip]
641 #[test]
642 fn owned_generic() {
643 let ast: syn::DeriveInput = parse_quote! {
644 struct Lorem<'a, T: Debug> where T: PartialEq { }
645 };
646 let generics = ast.generics;
647 let mut builder = default_builder!();
648 builder.generics = Some(&generics);
649 builder.pattern = BuilderPattern::Owned;
650 builder.must_derive_clone = false;
651
652 assert_eq!(
653 quote!(#builder).to_string(),
654 {
655 let mut result = quote!();
656
657 #[cfg(not(feature = "clippy"))]
658 result.append_all(quote!(#[allow(clippy::all)]));
659
660 result.append_all(quote!(
661 pub struct FooBuilder<'a, T: Debug> where T: PartialEq {
662 foo: u32,
663 }
664 ));
665
666 #[cfg(not(feature = "clippy"))]
667 result.append_all(quote!(#[allow(clippy::all)]));
668
669 result.append_all(quote!(
670 #[allow(dead_code)]
671 impl<'a, T: Debug> FooBuilder<'a, T> where T: PartialEq {
672 fn bar() -> {
673 unimplemented!()
674 }
675
676 /// Create an empty builder, with all fields set to `None` or `PhantomData`.
677 fn create_empty() -> Self {
678 Self {
679 foo: ::db::export::core::default::Default::default(),
680 }
681 }
682 }
683
684 impl<'a, T: Debug> ::db::export::core::default::Default for FooBuilder<'a, T>
685 where T: PartialEq {
686 fn default() -> Self {
687 Self::create_empty()
688 }
689 }
690 ));
691
692 add_generated_error(&mut result);
693
694 result
695 }.to_string()
696 );
697 }
698
699 #[test]
700 fn disabled() {
701 let mut builder = default_builder!();
702 builder.enabled = false;
703
704 assert_eq!(quote!(#builder).to_string(), quote!().to_string());
705 }
706
707 #[test]
708 fn add_derives() {
709 let derives = vec![parse_quote!(Serialize)];
710 let mut builder = default_builder!();
711 builder.derives = &derives;
712
713 assert_eq!(
714 quote!(#builder).to_string(),
715 {
716 let mut result = quote!();
717
718 #[cfg(not(feature = "clippy"))]
719 result.append_all(quote!(#[allow(clippy::all)]));
720
721 result.append_all(quote!(
722 #[derive(Clone, Serialize)]
723 pub struct FooBuilder {
724 foo: u32,
725 }
726 ));
727
728 #[cfg(not(feature = "clippy"))]
729 result.append_all(quote!(#[allow(clippy::all)]));
730
731 result.append_all(quote!(
732 #[allow(dead_code)]
733 impl FooBuilder {
734 fn bar () -> {
735 unimplemented!()
736 }
737
738 /// Create an empty builder, with all fields set to `None` or `PhantomData`.
739 fn create_empty() -> Self {
740 Self {
741 foo: ::db::export::core::default::Default::default(),
742 }
743 }
744 }
745
746 impl ::db::export::core::default::Default for FooBuilder {
747 fn default() -> Self {
748 Self::create_empty()
749 }
750 }
751 ));
752
753 add_generated_error(&mut result);
754
755 result
756 }
757 .to_string()
758 );
759 }
760}
761