| 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 | |