1 | use proc_macro2::{Span, TokenStream}; |
---|---|
2 | use quote::{ToTokens, TokenStreamExt}; |
3 | |
4 | use crate::{change_span, BlockContents, BuilderPattern, DefaultExpression, DEFAULT_STRUCT_NAME}; |
5 | |
6 | /// Initializer for the target struct fields, implementing `quote::ToTokens`. |
7 | /// |
8 | /// Lives in the body of `BuildMethod`. |
9 | /// |
10 | /// # Examples |
11 | /// |
12 | /// Will expand to something like the following (depending on settings): |
13 | /// |
14 | /// ```rust,ignore |
15 | /// # extern crate proc_macro2; |
16 | /// # #[macro_use] |
17 | /// # extern crate quote; |
18 | /// # extern crate syn; |
19 | /// # #[macro_use] |
20 | /// # extern crate derive_builder_core; |
21 | /// # use derive_builder_core::{DeprecationNotes, Initializer, BuilderPattern}; |
22 | /// # fn main() { |
23 | /// # let mut initializer = default_initializer!(); |
24 | /// # initializer.default_value = Some("42".parse().unwrap()); |
25 | /// # initializer.builder_pattern = BuilderPattern::Owned; |
26 | /// # |
27 | /// # assert_eq!(quote!(#initializer).to_string(), quote!( |
28 | /// foo: match self.foo { |
29 | /// Some(value) => value, |
30 | /// None => { 42 }, |
31 | /// }, |
32 | /// # ).to_string()); |
33 | /// # } |
34 | /// ``` |
35 | #[derive(Debug, Clone)] |
36 | pub struct Initializer<'a> { |
37 | /// Path to the root of the derive_builder crate. |
38 | pub crate_root: &'a syn::Path, |
39 | /// Name of the target field. |
40 | pub field_ident: &'a syn::Ident, |
41 | /// Whether the builder implements a setter for this field. |
42 | pub field_enabled: bool, |
43 | /// How the build method takes and returns `self` (e.g. mutably). |
44 | pub builder_pattern: BuilderPattern, |
45 | /// Default value for the target field. |
46 | /// |
47 | /// This takes precedence over a default struct identifier. |
48 | pub default_value: Option<&'a DefaultExpression>, |
49 | /// Whether the build_method defines a default struct. |
50 | pub use_default_struct: bool, |
51 | /// Span where the macro was told to use a preexisting error type, instead of creating one, |
52 | /// to represent failures of the `build` method. |
53 | /// |
54 | /// An initializer can force early-return if a field has no set value and no default is |
55 | /// defined. In these cases, it will convert from `derive_builder::UninitializedFieldError` |
56 | /// into the return type of its enclosing `build` method. That conversion is guaranteed to |
57 | /// work for generated error types, but if the caller specified an error type to use instead |
58 | /// they may have forgotten the conversion from `UninitializedFieldError` into their specified |
59 | /// error type. |
60 | pub custom_error_type_span: Option<Span>, |
61 | /// Method to use to to convert the builder's field to the target field |
62 | /// |
63 | /// For sub-builder fields, this will be `build` (or similar) |
64 | pub conversion: FieldConversion<'a>, |
65 | } |
66 | |
67 | impl<'a> ToTokens for Initializer<'a> { |
68 | fn to_tokens(&self, tokens: &mut TokenStream) { |
69 | let struct_field = &self.field_ident; |
70 | let builder_field = struct_field; |
71 | |
72 | // This structure prevents accidental failure to add the trailing `,` due to incautious `return` |
73 | let append_rhs = |tokens: &mut TokenStream| { |
74 | if !self.field_enabled { |
75 | let default = self.default(); |
76 | tokens.append_all(quote!( |
77 | #default |
78 | )); |
79 | } else { |
80 | match &self.conversion { |
81 | FieldConversion::Block(conv) => { |
82 | conv.to_tokens(tokens); |
83 | } |
84 | FieldConversion::Move => tokens.append_all(quote!( self.#builder_field )), |
85 | FieldConversion::OptionOrDefault => { |
86 | let match_some = self.match_some(); |
87 | let match_none = self.match_none(); |
88 | tokens.append_all(quote!( |
89 | match self.#builder_field { |
90 | #match_some, |
91 | #match_none, |
92 | } |
93 | )); |
94 | } |
95 | } |
96 | } |
97 | }; |
98 | |
99 | tokens.append_all(quote!(#struct_field:)); |
100 | append_rhs(tokens); |
101 | tokens.append_all(quote!(,)); |
102 | } |
103 | } |
104 | |
105 | impl<'a> Initializer<'a> { |
106 | /// To be used inside of `#struct_field: match self.#builder_field { ... }` |
107 | fn match_some(&'a self) -> MatchSome<'a> { |
108 | match self.builder_pattern { |
109 | BuilderPattern::Owned => MatchSome::Move, |
110 | BuilderPattern::Mutable | BuilderPattern::Immutable => MatchSome::Clone { |
111 | crate_root: self.crate_root, |
112 | }, |
113 | } |
114 | } |
115 | |
116 | /// To be used inside of `#struct_field: match self.#builder_field { ... }` |
117 | fn match_none(&'a self) -> MatchNone<'a> { |
118 | match self.default_value { |
119 | Some(expr) => MatchNone::DefaultTo { |
120 | expr, |
121 | crate_root: self.crate_root, |
122 | }, |
123 | None => { |
124 | if self.use_default_struct { |
125 | MatchNone::UseDefaultStructField(self.field_ident) |
126 | } else { |
127 | MatchNone::ReturnError { |
128 | crate_root: self.crate_root, |
129 | field_name: self.field_ident.to_string(), |
130 | span: self.custom_error_type_span, |
131 | } |
132 | } |
133 | } |
134 | } |
135 | } |
136 | |
137 | fn default(&'a self) -> TokenStream { |
138 | let crate_root = self.crate_root; |
139 | match self.default_value { |
140 | Some(expr) => expr.with_crate_root(crate_root).into_token_stream(), |
141 | None if self.use_default_struct => { |
142 | let struct_ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site()); |
143 | let field_ident = self.field_ident; |
144 | quote!(#struct_ident.#field_ident) |
145 | } |
146 | None => { |
147 | quote!(#crate_root::export::core::default::Default::default()) |
148 | } |
149 | } |
150 | } |
151 | } |
152 | |
153 | #[derive(Debug, Clone)] |
154 | pub enum FieldConversion<'a> { |
155 | /// Usual conversion: unwrap the Option from the builder, or (hope to) use a default value |
156 | OptionOrDefault, |
157 | /// Custom conversion is a block contents expression |
158 | Block(&'a BlockContents), |
159 | /// Custom conversion is just to move the field from the builder |
160 | Move, |
161 | } |
162 | |
163 | /// To be used inside of `#struct_field: match self.#builder_field { ... }` |
164 | enum MatchNone<'a> { |
165 | /// Inner value must be a valid Rust expression |
166 | DefaultTo { |
167 | expr: &'a DefaultExpression, |
168 | crate_root: &'a syn::Path, |
169 | }, |
170 | /// Inner value must be the field identifier |
171 | /// |
172 | /// The default struct must be in scope in the build_method. |
173 | UseDefaultStructField(&'a syn::Ident), |
174 | /// Inner value must be the field name |
175 | ReturnError { |
176 | crate_root: &'a syn::Path, |
177 | field_name: String, |
178 | span: Option<Span>, |
179 | }, |
180 | } |
181 | |
182 | impl<'a> ToTokens for MatchNone<'a> { |
183 | fn to_tokens(&self, tokens: &mut TokenStream) { |
184 | match *self { |
185 | MatchNone::DefaultTo { expr, crate_root } => { |
186 | let expr = expr.with_crate_root(crate_root); |
187 | tokens.append_all(quote!(None => #expr)); |
188 | } |
189 | MatchNone::UseDefaultStructField(field_ident) => { |
190 | let struct_ident = syn::Ident::new(DEFAULT_STRUCT_NAME, Span::call_site()); |
191 | tokens.append_all(quote!( |
192 | None => #struct_ident.#field_ident |
193 | )) |
194 | } |
195 | MatchNone::ReturnError { |
196 | ref field_name, |
197 | ref span, |
198 | crate_root, |
199 | } => { |
200 | let conv_span = span.unwrap_or_else(Span::call_site); |
201 | // If the conversion fails, the compiler error should point to the error declaration |
202 | // rather than the crate root declaration, but the compiler will see the span of #crate_root |
203 | // and produce an undesired behavior (possibly because that's the first span in the bad expression?). |
204 | // Creating a copy with deeply-rewritten spans preserves the desired error behavior. |
205 | let crate_root = change_span(crate_root.into_token_stream(), conv_span); |
206 | let err_conv = quote_spanned!(conv_span => #crate_root::export::core::convert::Into::into( |
207 | #crate_root::UninitializedFieldError::from(#field_name) |
208 | )); |
209 | tokens.append_all(quote!( |
210 | None => return #crate_root::export::core::result::Result::Err(#err_conv) |
211 | )); |
212 | } |
213 | } |
214 | } |
215 | } |
216 | |
217 | /// To be used inside of `#struct_field: match self.#builder_field { ... }` |
218 | enum MatchSome<'a> { |
219 | Move, |
220 | Clone { crate_root: &'a syn::Path }, |
221 | } |
222 | |
223 | impl ToTokens for MatchSome<'_> { |
224 | fn to_tokens(&self, tokens: &mut TokenStream) { |
225 | match *self { |
226 | Self::Move => tokens.append_all(iter:quote!( |
227 | Some(value) => value |
228 | )), |
229 | Self::Clone { crate_root: &Path } => tokens.append_all(iter:quote!( |
230 | Some(ref value) => #crate_root::export::core::clone::Clone::clone(value) |
231 | )), |
232 | } |
233 | } |
234 | } |
235 | |
236 | /// Helper macro for unit tests. This is _only_ public in order to be accessible |
237 | /// from doc-tests too. |
238 | #[doc(hidden)] |
239 | #[macro_export] |
240 | macro_rules! default_initializer { |
241 | () => { |
242 | Initializer { |
243 | // Deliberately don't use the default value here - make sure |
244 | // that all test cases are passing crate_root through properly. |
245 | crate_root: &parse_quote!(::db), |
246 | field_ident: &syn::Ident::new("foo", ::proc_macro2::Span::call_site()), |
247 | field_enabled: true, |
248 | builder_pattern: BuilderPattern::Mutable, |
249 | default_value: None, |
250 | use_default_struct: false, |
251 | conversion: FieldConversion::OptionOrDefault, |
252 | custom_error_type_span: None, |
253 | } |
254 | }; |
255 | } |
256 | |
257 | #[cfg(test)] |
258 | mod tests { |
259 | #[allow(unused_imports)] |
260 | use super::*; |
261 | |
262 | #[test] |
263 | fn immutable() { |
264 | let mut initializer = default_initializer!(); |
265 | initializer.builder_pattern = BuilderPattern::Immutable; |
266 | |
267 | assert_eq!( |
268 | quote!(#initializer).to_string(), |
269 | quote!( |
270 | foo: match self.foo { |
271 | Some(ref value) => ::db::export::core::clone::Clone::clone(value), |
272 | None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into( |
273 | ::db::UninitializedFieldError::from("foo") |
274 | )), |
275 | }, |
276 | ) |
277 | .to_string() |
278 | ); |
279 | } |
280 | |
281 | #[test] |
282 | fn mutable() { |
283 | let mut initializer = default_initializer!(); |
284 | initializer.builder_pattern = BuilderPattern::Mutable; |
285 | |
286 | assert_eq!( |
287 | quote!(#initializer).to_string(), |
288 | quote!( |
289 | foo: match self.foo { |
290 | Some(ref value) => ::db::export::core::clone::Clone::clone(value), |
291 | None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into( |
292 | ::db::UninitializedFieldError::from("foo") |
293 | )), |
294 | }, |
295 | ) |
296 | .to_string() |
297 | ); |
298 | } |
299 | |
300 | #[test] |
301 | fn owned() { |
302 | let mut initializer = default_initializer!(); |
303 | initializer.builder_pattern = BuilderPattern::Owned; |
304 | |
305 | assert_eq!( |
306 | quote!(#initializer).to_string(), |
307 | quote!( |
308 | foo: match self.foo { |
309 | Some(value) => value, |
310 | None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into( |
311 | ::db::UninitializedFieldError::from("foo") |
312 | )), |
313 | }, |
314 | ) |
315 | .to_string() |
316 | ); |
317 | } |
318 | |
319 | #[test] |
320 | fn default_value() { |
321 | let mut initializer = default_initializer!(); |
322 | let default_value = DefaultExpression::explicit::<syn::Expr>(parse_quote!(42)); |
323 | initializer.default_value = Some(&default_value); |
324 | |
325 | assert_eq!( |
326 | quote!(#initializer).to_string(), |
327 | quote!( |
328 | foo: match self.foo { |
329 | Some(ref value) => ::db::export::core::clone::Clone::clone(value), |
330 | None => { 42 }, |
331 | }, |
332 | ) |
333 | .to_string() |
334 | ); |
335 | } |
336 | |
337 | #[test] |
338 | fn default_struct() { |
339 | let mut initializer = default_initializer!(); |
340 | initializer.use_default_struct = true; |
341 | |
342 | assert_eq!( |
343 | quote!(#initializer).to_string(), |
344 | quote!( |
345 | foo: match self.foo { |
346 | Some(ref value) => ::db::export::core::clone::Clone::clone(value), |
347 | None => __default.foo, |
348 | }, |
349 | ) |
350 | .to_string() |
351 | ); |
352 | } |
353 | |
354 | #[test] |
355 | fn setter_disabled() { |
356 | let mut initializer = default_initializer!(); |
357 | initializer.field_enabled = false; |
358 | |
359 | assert_eq!( |
360 | quote!(#initializer).to_string(), |
361 | quote!(foo: ::db::export::core::default::Default::default(),).to_string() |
362 | ); |
363 | } |
364 | |
365 | #[test] |
366 | fn no_std() { |
367 | let initializer = default_initializer!(); |
368 | |
369 | assert_eq!( |
370 | quote!(#initializer).to_string(), |
371 | quote!( |
372 | foo: match self.foo { |
373 | Some(ref value) => ::db::export::core::clone::Clone::clone(value), |
374 | None => return ::db::export::core::result::Result::Err(::db::export::core::convert::Into::into( |
375 | ::db::UninitializedFieldError::from("foo") |
376 | )), |
377 | }, |
378 | ) |
379 | .to_string() |
380 | ); |
381 | } |
382 | } |
383 |
Definitions
- Initializer
- crate_root
- field_ident
- field_enabled
- builder_pattern
- default_value
- use_default_struct
- custom_error_type_span
- conversion
- to_tokens
- match_some
- match_none
- default
- FieldConversion
- OptionOrDefault
- Block
- Move
- MatchNone
- DefaultTo
- expr
- crate_root
- UseDefaultStructField
- ReturnError
- crate_root
- field_name
- span
- to_tokens
- expr
- crate_root
- field_name
- span
- crate_root
- MatchSome
- Move
- Clone
- crate_root
- to_tokens
- crate_root
Learn Rust with the experts
Find out more