1 | use std::borrow::Cow; |
---|---|
2 | |
3 | use proc_macro2::TokenStream; |
4 | use quote::{format_ident, ToTokens, TokenStreamExt}; |
5 | use syn::punctuated::Punctuated; |
6 | use syn::{Path, TraitBound, TraitBoundModifier, TypeParamBound}; |
7 | |
8 | use crate::{doc_comment_from, BuildMethod, BuilderField, BuilderPattern, Setter}; |
9 | |
10 | const ALLOC_NOT_ENABLED_ERROR: &str = r#"`alloc` is disabled within 'derive_builder', consider one of the following: |
11 | * enable feature `alloc` on 'dervie_builder' if a `global_allocator` is present |
12 | * use a custom error `#[builder(build_fn(error = "path::to::Error"))] |
13 | * disable the validation error `#[builder(build_fn(error(validation_error = false)))]"#; |
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)] |
88 | pub 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 to include `ValidationError` in the generated enum. Necessary to avoid dependency |
133 | /// on `alloc::string`. |
134 | /// |
135 | /// This would be `false` when `build_fn.error.as_validation_error() == Some((false, _))`. This |
136 | /// has no effect when `generate_error` is `false`. |
137 | pub generate_validation_error: bool, |
138 | /// Indicator of `cfg!(not(any(feature = "alloc", feature = "std")))`, as a field for tests |
139 | pub no_alloc: bool, |
140 | /// Whether this builder must derive `Clone`. |
141 | /// |
142 | /// This is true even for a builder using the `owned` pattern if there is a field whose setter |
143 | /// uses a different pattern. |
144 | pub must_derive_clone: bool, |
145 | /// Doc-comment of the builder struct. |
146 | pub doc_comment: Option<syn::Attribute>, |
147 | /// Whether or not a libstd is used. |
148 | pub std: bool, |
149 | } |
150 | |
151 | impl<'a> ToTokens for Builder<'a> { |
152 | fn to_tokens(&self, tokens: &mut TokenStream) { |
153 | if self.enabled { |
154 | let crate_root = self.crate_root; |
155 | let builder_vis = &self.visibility; |
156 | let builder_ident = &self.ident; |
157 | // Splitting because Generics doesn't output WhereClause, see dtolnay/syn#782 |
158 | let (struct_generics, struct_where_clause) = ( |
159 | self.generics, |
160 | self.generics.and_then(|g| g.where_clause.as_ref()), |
161 | ); |
162 | let bounded_generics = self.compute_impl_bounds(); |
163 | let (impl_generics, impl_ty_generics, impl_where_clause) = |
164 | bounded_generics.split_for_impl(); |
165 | let builder_fields = &self.fields; |
166 | let builder_field_initializers = &self.field_initializers; |
167 | let create_empty = &self.create_empty; |
168 | let functions = &self.functions; |
169 | |
170 | // Create the comma-separated set of derived traits for the builder |
171 | let derive_attr = { |
172 | let clone_trait: Path = parse_quote!(Clone); |
173 | |
174 | let mut traits: Punctuated<&Path, Token![,]> = Default::default(); |
175 | if self.must_derive_clone { |
176 | traits.push(&clone_trait); |
177 | } |
178 | traits.extend(self.derives); |
179 | |
180 | if traits.is_empty() { |
181 | quote!() |
182 | } else { |
183 | quote!(#[derive(#traits)]) |
184 | } |
185 | }; |
186 | |
187 | let struct_attrs = self.struct_attrs; |
188 | let impl_attrs = self.impl_attrs; |
189 | |
190 | let builder_doc_comment = &self.doc_comment; |
191 | |
192 | #[cfg(not(feature = "clippy"))] |
193 | tokens.append_all(quote!(#[allow(clippy::all)])); |
194 | |
195 | // struct_attrs MUST come after derive_attr, otherwise attributes for a derived |
196 | // trait will appear before its derivation. As of rustc 1.59.0 this is a compiler |
197 | // warning; see https://github.com/rust-lang/rust/issues/79202 |
198 | tokens.append_all(quote!( |
199 | #derive_attr |
200 | #(#struct_attrs)* |
201 | #builder_doc_comment |
202 | #builder_vis struct #builder_ident #struct_generics #struct_where_clause { |
203 | #(#builder_fields)* |
204 | } |
205 | )); |
206 | |
207 | #[cfg(not(feature = "clippy"))] |
208 | tokens.append_all(quote!(#[allow(clippy::all)])); |
209 | |
210 | tokens.append_all(quote!( |
211 | #(#impl_attrs)* |
212 | #[allow(dead_code)] |
213 | impl #impl_generics #builder_ident #impl_ty_generics #impl_where_clause { |
214 | #(#functions)* |
215 | |
216 | /// Create an empty builder, with all fields set to `None` or `PhantomData`. |
217 | fn #create_empty() -> Self { |
218 | Self { |
219 | #(#builder_field_initializers)* |
220 | } |
221 | } |
222 | } |
223 | )); |
224 | |
225 | if self.impl_default { |
226 | tokens.append_all(quote!( |
227 | impl #impl_generics #crate_root::export::core::default::Default for #builder_ident #impl_ty_generics #impl_where_clause { |
228 | fn default() -> Self { |
229 | Self::#create_empty() |
230 | } |
231 | } |
232 | )); |
233 | } |
234 | |
235 | if self.no_alloc && self.generate_error && self.generate_validation_error { |
236 | let err = syn::Error::new_spanned(&self.ident, ALLOC_NOT_ENABLED_ERROR); |
237 | tokens.append_all(err.to_compile_error()); |
238 | } else if self.generate_error { |
239 | let builder_error_ident = format_ident!("{} Error", builder_ident); |
240 | let builder_error_doc = format!("Error type for{} ", builder_ident); |
241 | |
242 | let validation_error = if self.generate_validation_error { |
243 | quote!( |
244 | /// Custom validation error |
245 | ValidationError(#crate_root::export::core::string::String), |
246 | ) |
247 | } else { |
248 | TokenStream::new() |
249 | }; |
250 | let validation_from = if self.generate_validation_error { |
251 | quote!( |
252 | impl #crate_root::export::core::convert::From<#crate_root::export::core::string::String> for #builder_error_ident { |
253 | fn from(s: #crate_root::export::core::string::String) -> Self { |
254 | Self::ValidationError(s) |
255 | } |
256 | } |
257 | ) |
258 | } else { |
259 | TokenStream::new() |
260 | }; |
261 | let validation_display = if self.generate_validation_error { |
262 | quote!( |
263 | Self::ValidationError(ref error) => write!(f, "{}", error), |
264 | ) |
265 | } else { |
266 | TokenStream::new() |
267 | }; |
268 | |
269 | tokens.append_all(quote!( |
270 | #[doc=#builder_error_doc] |
271 | #[derive(Debug)] |
272 | #[non_exhaustive] |
273 | #builder_vis enum #builder_error_ident { |
274 | /// Uninitialized field |
275 | UninitializedField(&'static str), |
276 | #validation_error |
277 | } |
278 | |
279 | impl #crate_root::export::core::convert::From<#crate_root::UninitializedFieldError> for #builder_error_ident { |
280 | fn from(s: #crate_root::UninitializedFieldError) -> Self { |
281 | Self::UninitializedField(s.field_name()) |
282 | } |
283 | } |
284 | |
285 | #validation_from |
286 | |
287 | impl #crate_root::export::core::fmt::Display for #builder_error_ident { |
288 | fn fmt(&self, f: &mut #crate_root::export::core::fmt::Formatter) -> #crate_root::export::core::fmt::Result { |
289 | match self { |
290 | Self::UninitializedField(ref field) => write!(f, "`{}` must be initialized", field), |
291 | #validation_display |
292 | } |
293 | } |
294 | } |
295 | )); |
296 | |
297 | if self.std { |
298 | tokens.append_all(quote!( |
299 | impl std::error::Error for #builder_error_ident {} |
300 | )); |
301 | } |
302 | } |
303 | } |
304 | } |
305 | } |
306 | |
307 | impl<'a> Builder<'a> { |
308 | /// Set a doc-comment for this item. |
309 | pub fn doc_comment(&mut self, s: String) -> &mut Self { |
310 | self.doc_comment = Some(doc_comment_from(s)); |
311 | self |
312 | } |
313 | |
314 | /// Add a field to the builder |
315 | pub fn push_field(&mut self, f: BuilderField) -> &mut Self { |
316 | self.fields.push(quote!(#f)); |
317 | self.field_initializers.push(f.default_initializer_tokens()); |
318 | self |
319 | } |
320 | |
321 | /// Add a setter function to the builder |
322 | pub fn push_setter_fn(&mut self, f: Setter) -> &mut Self { |
323 | self.functions.push(quote!(#f)); |
324 | self |
325 | } |
326 | |
327 | /// Add final build function to the builder |
328 | pub fn push_build_fn(&mut self, f: BuildMethod) -> &mut Self { |
329 | self.functions.push(quote!(#f)); |
330 | self |
331 | } |
332 | |
333 | /// Add `Clone` trait bound to generic types for non-owned builders. |
334 | /// This enables target types to declare generics without requiring a |
335 | /// `Clone` impl. This is the same as how the built-in derives for |
336 | /// `Clone`, `Default`, `PartialEq`, and other traits work. |
337 | fn compute_impl_bounds(&self) -> syn::Generics { |
338 | if let Some(type_gen) = self.generics { |
339 | let mut generics = type_gen.clone(); |
340 | |
341 | if !self.pattern.requires_clone() || type_gen.type_params().next().is_none() { |
342 | return generics; |
343 | } |
344 | |
345 | let crate_root = self.crate_root; |
346 | |
347 | let clone_bound = TypeParamBound::Trait(TraitBound { |
348 | paren_token: None, |
349 | modifier: TraitBoundModifier::None, |
350 | lifetimes: None, |
351 | path: syn::parse_quote!(#crate_root::export::core::clone::Clone), |
352 | }); |
353 | |
354 | for typ in generics.type_params_mut() { |
355 | typ.bounds.push(clone_bound.clone()); |
356 | } |
357 | |
358 | generics |
359 | } else { |
360 | Default::default() |
361 | } |
362 | } |
363 | } |
364 | |
365 | /// Helper macro for unit tests. This is _only_ public in order to be accessible |
366 | /// from doc-tests too. |
367 | #[doc(hidden)] |
368 | #[macro_export] |
369 | macro_rules! default_builder { |
370 | () => { |
371 | Builder { |
372 | // Deliberately don't use the default value here - make sure |
373 | // that all test cases are passing crate_root through properly. |
374 | crate_root: &parse_quote!(::db), |
375 | enabled: true, |
376 | ident: syn::Ident::new("FooBuilder", ::proc_macro2::Span::call_site()), |
377 | pattern: Default::default(), |
378 | derives: &vec![], |
379 | struct_attrs: &vec![], |
380 | impl_attrs: &vec![], |
381 | impl_default: true, |
382 | create_empty: syn::Ident::new("create_empty", ::proc_macro2::Span::call_site()), |
383 | generics: None, |
384 | visibility: ::std::borrow::Cow::Owned(parse_quote!(pub)), |
385 | fields: vec![quote!(foo: u32,)], |
386 | field_initializers: vec![quote!(foo: ::db::export::core::default::Default::default(), )], |
387 | functions: vec![quote!(fn bar() -> { unimplemented!() })], |
388 | generate_error: true, |
389 | generate_validation_error: true, |
390 | no_alloc: false, |
391 | must_derive_clone: true, |
392 | doc_comment: None, |
393 | std: true, |
394 | } |
395 | }; |
396 | } |
397 | |
398 | #[cfg(test)] |
399 | mod tests { |
400 | #[allow(unused_imports)] |
401 | use super::*; |
402 | use syn::Ident; |
403 | |
404 | fn add_simple_foo_builder(result: &mut TokenStream) { |
405 | #[cfg(not(feature = "clippy"))] |
406 | result.append_all(quote!(#[allow(clippy::all)])); |
407 | |
408 | result.append_all(quote!( |
409 | #[derive(Clone)] |
410 | pub struct FooBuilder { |
411 | foo: u32, |
412 | } |
413 | )); |
414 | |
415 | #[cfg(not(feature = "clippy"))] |
416 | result.append_all(quote!(#[allow(clippy::all)])); |
417 | |
418 | result.append_all(quote!( |
419 | #[allow(dead_code)] |
420 | impl FooBuilder { |
421 | fn bar () -> { |
422 | unimplemented!() |
423 | } |
424 | |
425 | /// Create an empty builder, with all fields set to `None` or `PhantomData`. |
426 | fn create_empty() -> Self { |
427 | Self { |
428 | foo: ::db::export::core::default::Default::default(), |
429 | } |
430 | } |
431 | } |
432 | |
433 | impl ::db::export::core::default::Default for FooBuilder { |
434 | fn default() -> Self { |
435 | Self::create_empty() |
436 | } |
437 | } |
438 | )); |
439 | } |
440 | |
441 | fn add_generated_error(result: &mut TokenStream) { |
442 | result.append_all(quote!( |
443 | #[doc="Error type for FooBuilder"] |
444 | #[derive(Debug)] |
445 | #[non_exhaustive] |
446 | pub enum FooBuilderError { |
447 | /// Uninitialized field |
448 | UninitializedField(&'static str), |
449 | /// Custom validation error |
450 | ValidationError(::db::export::core::string::String), |
451 | } |
452 | |
453 | impl ::db::export::core::convert::From<::db::UninitializedFieldError> for FooBuilderError { |
454 | fn from(s: ::db::UninitializedFieldError) -> Self { |
455 | Self::UninitializedField(s.field_name()) |
456 | } |
457 | } |
458 | |
459 | impl ::db::export::core::convert::From<::db::export::core::string::String> for FooBuilderError { |
460 | fn from(s: ::db::export::core::string::String) -> Self { |
461 | Self::ValidationError(s) |
462 | } |
463 | } |
464 | |
465 | impl ::db::export::core::fmt::Display for FooBuilderError { |
466 | fn fmt(&self, f: &mut ::db::export::core::fmt::Formatter) -> ::db::export::core::fmt::Result { |
467 | match self { |
468 | Self::UninitializedField(ref field) => write!(f, "`{}` must be initialized", field), |
469 | Self::ValidationError(ref error) => write!(f, "{}", error), |
470 | } |
471 | } |
472 | } |
473 | |
474 | impl std::error::Error for FooBuilderError {} |
475 | )); |
476 | } |
477 | |
478 | #[test] |
479 | fn simple() { |
480 | let builder = default_builder!(); |
481 | |
482 | assert_eq!( |
483 | quote!(#builder).to_string(), |
484 | { |
485 | let mut result = quote!(); |
486 | |
487 | add_simple_foo_builder(&mut result); |
488 | |
489 | add_generated_error(&mut result); |
490 | |
491 | result |
492 | } |
493 | .to_string() |
494 | ); |
495 | } |
496 | |
497 | #[test] |
498 | fn rename_create_empty() { |
499 | let mut builder = default_builder!(); |
500 | builder.create_empty = Ident::new("empty", proc_macro2::Span::call_site()); |
501 | |
502 | assert_eq!( |
503 | quote!(#builder).to_string(), |
504 | { |
505 | let mut result = quote!(); |
506 | |
507 | #[cfg(not(feature = "clippy"))] |
508 | result.append_all(quote!(#[allow(clippy::all)])); |
509 | |
510 | result.append_all(quote!( |
511 | #[derive(Clone)] |
512 | pub struct FooBuilder { |
513 | foo: u32, |
514 | } |
515 | )); |
516 | |
517 | #[cfg(not(feature = "clippy"))] |
518 | result.append_all(quote!(#[allow(clippy::all)])); |
519 | |
520 | result.append_all(quote!( |
521 | #[allow(dead_code)] |
522 | impl FooBuilder { |
523 | fn bar () -> { |
524 | unimplemented!() |
525 | } |
526 | |
527 | /// Create an empty builder, with all fields set to `None` or `PhantomData`. |
528 | fn empty() -> Self { |
529 | Self { |
530 | foo: ::db::export::core::default::Default::default(), |
531 | } |
532 | } |
533 | } |
534 | |
535 | impl ::db::export::core::default::Default for FooBuilder { |
536 | fn default() -> Self { |
537 | Self::empty() |
538 | } |
539 | } |
540 | )); |
541 | |
542 | add_generated_error(&mut result); |
543 | |
544 | result |
545 | } |
546 | .to_string() |
547 | ); |
548 | } |
549 | |
550 | // This test depends on the exact formatting of the `stringify`'d code, |
551 | // so we don't automatically format the test |
552 | #[rustfmt::skip] |
553 | #[test] |
554 | fn generic() { |
555 | let ast: syn::DeriveInput = parse_quote! { |
556 | struct Lorem<'a, T: Debug> where T: PartialEq { } |
557 | }; |
558 | let generics = ast.generics; |
559 | let mut builder = default_builder!(); |
560 | builder.generics = Some(&generics); |
561 | |
562 | assert_eq!( |
563 | quote!(#builder).to_string(), |
564 | { |
565 | let mut result = quote!(); |
566 | |
567 | #[cfg(not(feature = "clippy"))] |
568 | result.append_all(quote!(#[allow(clippy::all)])); |
569 | |
570 | result.append_all(quote!( |
571 | #[derive(Clone)] |
572 | pub struct FooBuilder<'a, T: Debug> where T: PartialEq { |
573 | foo: u32, |
574 | } |
575 | )); |
576 | |
577 | #[cfg(not(feature = "clippy"))] |
578 | result.append_all(quote!(#[allow(clippy::all)])); |
579 | |
580 | result.append_all(quote!( |
581 | #[allow(dead_code)] |
582 | impl<'a, T: Debug + ::db::export::core::clone::Clone> FooBuilder<'a, T> where T: PartialEq { |
583 | fn bar() -> { |
584 | unimplemented!() |
585 | } |
586 | |
587 | /// Create an empty builder, with all fields set to `None` or `PhantomData`. |
588 | fn create_empty() -> Self { |
589 | Self { |
590 | foo: ::db::export::core::default::Default::default(), |
591 | } |
592 | } |
593 | } |
594 | |
595 | impl<'a, T: Debug + ::db::export::core::clone::Clone> ::db::export::core::default::Default for FooBuilder<'a, T> where T: PartialEq { |
596 | fn default() -> Self { |
597 | Self::create_empty() |
598 | } |
599 | } |
600 | )); |
601 | |
602 | add_generated_error(&mut result); |
603 | |
604 | result |
605 | }.to_string() |
606 | ); |
607 | } |
608 | |
609 | // This test depends on the exact formatting of the `stringify`'d code, |
610 | // so we don't automatically format the test |
611 | #[rustfmt::skip] |
612 | #[test] |
613 | fn generic_reference() { |
614 | let ast: syn::DeriveInput = parse_quote! { |
615 | struct Lorem<'a, T: 'a + Default> where T: PartialEq{ } |
616 | }; |
617 | |
618 | let generics = ast.generics; |
619 | let mut builder = default_builder!(); |
620 | builder.generics = Some(&generics); |
621 | |
622 | assert_eq!( |
623 | quote!(#builder).to_string(), |
624 | { |
625 | let mut result = quote!(); |
626 | |
627 | #[cfg(not(feature = "clippy"))] |
628 | result.append_all(quote!(#[allow(clippy::all)])); |
629 | |
630 | result.append_all(quote!( |
631 | #[derive(Clone)] |
632 | pub struct FooBuilder<'a, T: 'a + Default> where T: PartialEq { |
633 | foo: u32, |
634 | } |
635 | )); |
636 | |
637 | #[cfg(not(feature = "clippy"))] |
638 | result.append_all(quote!(#[allow(clippy::all)])); |
639 | |
640 | result.append_all(quote!( |
641 | #[allow(dead_code)] |
642 | impl<'a, T: 'a + Default + ::db::export::core::clone::Clone> FooBuilder<'a, T> |
643 | where |
644 | T: PartialEq |
645 | { |
646 | fn bar() -> { |
647 | unimplemented!() |
648 | } |
649 | |
650 | /// Create an empty builder, with all fields set to `None` or `PhantomData`. |
651 | fn create_empty() -> Self { |
652 | Self { |
653 | foo: ::db::export::core::default::Default::default(), |
654 | } |
655 | } |
656 | } |
657 | |
658 | impl<'a, T: 'a + Default + ::db::export::core::clone::Clone> ::db::export::core::default::Default for FooBuilder<'a, T> where T: PartialEq { |
659 | fn default() -> Self { |
660 | Self::create_empty() |
661 | } |
662 | } |
663 | )); |
664 | |
665 | add_generated_error(&mut result); |
666 | |
667 | result |
668 | }.to_string() |
669 | ); |
670 | } |
671 | |
672 | // This test depends on the exact formatting of the `stringify`'d code, |
673 | // so we don't automatically format the test |
674 | #[rustfmt::skip] |
675 | #[test] |
676 | fn generic_with_default_type() { |
677 | let ast: syn::DeriveInput = parse_quote! { |
678 | struct Lorem<T = ()> { } |
679 | }; |
680 | |
681 | let generics = ast.generics; |
682 | let mut builder = default_builder!(); |
683 | builder.generics = Some(&generics); |
684 | |
685 | assert_eq!( |
686 | quote!(#builder).to_string(), |
687 | { |
688 | let mut result = quote!(); |
689 | |
690 | #[cfg(not(feature = "clippy"))] |
691 | result.append_all(quote!(#[allow(clippy::all)])); |
692 | |
693 | result.append_all(quote!( |
694 | #[derive(Clone)] |
695 | pub struct FooBuilder<T = ()> { |
696 | foo: u32, |
697 | } |
698 | )); |
699 | |
700 | #[cfg(not(feature = "clippy"))] |
701 | result.append_all(quote!(#[allow(clippy::all)])); |
702 | |
703 | result.append_all(quote!( |
704 | #[allow(dead_code)] |
705 | impl<T: ::db::export::core::clone::Clone> FooBuilder<T> |
706 | { |
707 | fn bar() -> { |
708 | unimplemented!() |
709 | } |
710 | |
711 | /// Create an empty builder, with all fields set to `None` or `PhantomData`. |
712 | fn create_empty() -> Self { |
713 | Self { |
714 | foo: ::db::export::core::default::Default::default(), |
715 | } |
716 | } |
717 | } |
718 | |
719 | impl<T: ::db::export::core::clone::Clone> ::db::export::core::default::Default for FooBuilder<T> { |
720 | fn default() -> Self { |
721 | Self::create_empty() |
722 | } |
723 | } |
724 | )); |
725 | |
726 | add_generated_error(&mut result); |
727 | |
728 | result |
729 | }.to_string() |
730 | ); |
731 | } |
732 | |
733 | // This test depends on the exact formatting of the `stringify`'d code, |
734 | // so we don't automatically format the test |
735 | #[rustfmt::skip] |
736 | #[test] |
737 | fn owned_generic() { |
738 | let ast: syn::DeriveInput = parse_quote! { |
739 | struct Lorem<'a, T: Debug> where T: PartialEq { } |
740 | }; |
741 | let generics = ast.generics; |
742 | let mut builder = default_builder!(); |
743 | builder.generics = Some(&generics); |
744 | builder.pattern = BuilderPattern::Owned; |
745 | builder.must_derive_clone = false; |
746 | |
747 | assert_eq!( |
748 | quote!(#builder).to_string(), |
749 | { |
750 | let mut result = quote!(); |
751 | |
752 | #[cfg(not(feature = "clippy"))] |
753 | result.append_all(quote!(#[allow(clippy::all)])); |
754 | |
755 | result.append_all(quote!( |
756 | pub struct FooBuilder<'a, T: Debug> where T: PartialEq { |
757 | foo: u32, |
758 | } |
759 | )); |
760 | |
761 | #[cfg(not(feature = "clippy"))] |
762 | result.append_all(quote!(#[allow(clippy::all)])); |
763 | |
764 | result.append_all(quote!( |
765 | #[allow(dead_code)] |
766 | impl<'a, T: Debug> FooBuilder<'a, T> where T: PartialEq { |
767 | fn bar() -> { |
768 | unimplemented!() |
769 | } |
770 | |
771 | /// Create an empty builder, with all fields set to `None` or `PhantomData`. |
772 | fn create_empty() -> Self { |
773 | Self { |
774 | foo: ::db::export::core::default::Default::default(), |
775 | } |
776 | } |
777 | } |
778 | |
779 | impl<'a, T: Debug> ::db::export::core::default::Default for FooBuilder<'a, T> |
780 | where T: PartialEq { |
781 | fn default() -> Self { |
782 | Self::create_empty() |
783 | } |
784 | } |
785 | )); |
786 | |
787 | add_generated_error(&mut result); |
788 | |
789 | result |
790 | }.to_string() |
791 | ); |
792 | } |
793 | |
794 | #[test] |
795 | fn disabled() { |
796 | let mut builder = default_builder!(); |
797 | builder.enabled = false; |
798 | |
799 | assert_eq!(quote!(#builder).to_string(), quote!().to_string()); |
800 | } |
801 | |
802 | #[test] |
803 | fn add_derives() { |
804 | let derives = vec![parse_quote!(Serialize)]; |
805 | let mut builder = default_builder!(); |
806 | builder.derives = &derives; |
807 | |
808 | assert_eq!( |
809 | quote!(#builder).to_string(), |
810 | { |
811 | let mut result = quote!(); |
812 | |
813 | #[cfg(not(feature = "clippy"))] |
814 | result.append_all(quote!(#[allow(clippy::all)])); |
815 | |
816 | result.append_all(quote!( |
817 | #[derive(Clone, Serialize)] |
818 | pub struct FooBuilder { |
819 | foo: u32, |
820 | } |
821 | )); |
822 | |
823 | #[cfg(not(feature = "clippy"))] |
824 | result.append_all(quote!(#[allow(clippy::all)])); |
825 | |
826 | result.append_all(quote!( |
827 | #[allow(dead_code)] |
828 | impl FooBuilder { |
829 | fn bar () -> { |
830 | unimplemented!() |
831 | } |
832 | |
833 | /// Create an empty builder, with all fields set to `None` or `PhantomData`. |
834 | fn create_empty() -> Self { |
835 | Self { |
836 | foo: ::db::export::core::default::Default::default(), |
837 | } |
838 | } |
839 | } |
840 | |
841 | impl ::db::export::core::default::Default for FooBuilder { |
842 | fn default() -> Self { |
843 | Self::create_empty() |
844 | } |
845 | } |
846 | )); |
847 | |
848 | add_generated_error(&mut result); |
849 | |
850 | result |
851 | } |
852 | .to_string() |
853 | ); |
854 | } |
855 | |
856 | #[test] |
857 | fn no_validation_error() { |
858 | let mut builder = default_builder!(); |
859 | builder.generate_validation_error = false; |
860 | |
861 | assert_eq!( |
862 | quote!(#builder).to_string(), |
863 | { |
864 | let mut result = quote!(); |
865 | |
866 | add_simple_foo_builder(&mut result); |
867 | |
868 | result.append_all(quote!( |
869 | #[doc="Error type for FooBuilder"] |
870 | #[derive(Debug)] |
871 | #[non_exhaustive] |
872 | pub enum FooBuilderError { |
873 | /// Uninitialized field |
874 | UninitializedField(&'static str), |
875 | } |
876 | |
877 | impl ::db::export::core::convert::From<::db::UninitializedFieldError> for FooBuilderError { |
878 | fn from(s: ::db::UninitializedFieldError) -> Self { |
879 | Self::UninitializedField(s.field_name()) |
880 | } |
881 | } |
882 | |
883 | impl ::db::export::core::fmt::Display for FooBuilderError { |
884 | fn fmt(&self, f: &mut ::db::export::core::fmt::Formatter) -> ::db::export::core::fmt::Result { |
885 | match self { |
886 | Self::UninitializedField(ref field) => write!(f, "`{}` must be initialized", field), |
887 | } |
888 | } |
889 | } |
890 | |
891 | impl std::error::Error for FooBuilderError {} |
892 | )); |
893 | |
894 | result |
895 | } |
896 | .to_string() |
897 | ); |
898 | } |
899 | |
900 | #[test] |
901 | fn no_alloc_bug_using_string() { |
902 | let mut builder = default_builder!(); |
903 | builder.no_alloc = true; |
904 | |
905 | assert_eq!( |
906 | quote!(#builder).to_string(), |
907 | { |
908 | let mut result = quote!(); |
909 | |
910 | add_simple_foo_builder(&mut result); |
911 | |
912 | result.append_all(quote!(::core::compile_error! { #ALLOC_NOT_ENABLED_ERROR })); |
913 | |
914 | result |
915 | } |
916 | .to_string() |
917 | ); |
918 | } |
919 | } |
920 |
Definitions
Learn Rust with the experts
Find out more