diff --git a/examples/example.rs b/examples/example.rs index 0027ecfc..8474b439 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,5 +1,15 @@ +use std::{collections::HashMap, iter}; + use typed_builder::TypedBuilder; +macro_rules! extend { + [$init:expr; $($expr:expr),*$(,)?] => {{ + let mut e = $init; + $(e.extend(iter::once($expr));)* + e + }}; +} + #[derive(PartialEq, TypedBuilder)] struct Foo { // Mandatory Field: @@ -13,16 +23,82 @@ struct Foo { // Or you can set the default #[builder(default = 20)] z: i32, + + // #[builder(default)] without parameter - don't require this field + // #[builder(setter(extend))] without parameter - start with the default and extend from there + #[builder(default, setter(extend(from_first, item_name = i0)))] + v0: Vec, + + // No `default`: This field must be set at least once. + // You can explicitly create the collection from the first item (but this is not required even without `default`). + #[builder(setter(extend(from_first = |first| vec![first])))] + v1: Vec, + + // Other `Extend` types are also supported. + #[builder(default, setter(extend))] + h: HashMap, } fn main() { - assert!(Foo::builder().x(1).y(2).z(3).build() == Foo { x: 1, y: Some(2), z: 3 }); + assert!( + Foo::builder().x(1).y(2).z(3).i0(4).v1_item(5).h_item((6, 7)).build() + == Foo { + x: 1, + y: Some(2), + z: 3, + v0: vec![4], + v1: vec![5], + h: extend![HashMap::new(); (6, 7)], + } + ); // Change the order of construction: - assert!(Foo::builder().z(1).x(2).y(3).build() == Foo { x: 2, y: Some(3), z: 1 }); + assert!( + Foo::builder().z(1).x(2).h_item((3, 4)).v1_item(5).i0(6).y(7).build() + == Foo { + x: 2, + y: Some(7), + z: 1, + v0: vec![6], + v1: vec![5], + h: extend![HashMap::new(); (3, 4)], + } + ); // Optional fields are optional: - assert!(Foo::builder().x(1).build() == Foo { x: 1, y: None, z: 20 }); + assert!( + Foo::builder().x(1).v1_item(2).build() + == Foo { + x: 1, + y: None, + z: 20, + v0: vec![], + v1: vec![2], + h: HashMap::new(), + } + ); + + // Extend fields can be set multiple times: + assert!( + Foo::builder() + .x(1) + .i0(2) + .i0(3) + .i0(4) + .v1_item(5) + .v1_item(6) + .h_item((7, 8)) + .h_item((9, 10)) + .build() + == Foo { + x: 1, + y: None, + z: 20, + v0: vec![3, 4], + v1: vec![5, 6], + h: extend![HashMap::new(); (7, 8), (9, 10)], + } + ); // This will not compile - because we did not set x: // Foo::builder().build(); diff --git a/src/field_info.rs b/src/field_info.rs index 1be21a0e..8244248e 100644 --- a/src/field_info.rs +++ b/src/field_info.rs @@ -3,7 +3,7 @@ use quote::quote; use syn::parse::Error; use syn::spanned::Spanned; -use crate::util::{expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix}; +use crate::util::{expr_to_single_string, ident_to_type, path_to_single_ident, path_to_single_string, strip_raw_ident_prefix}; #[derive(Debug)] pub struct FieldInfo<'a> { @@ -34,44 +34,78 @@ impl<'a> FieldInfo<'a> { syn::GenericParam::Type(self.generic_ident.clone().into()) } + pub fn item_name(&self) -> String { + self.builder_attr + .setter + .extend + .as_ref() + .expect("Tried to retrieve item_name() on FieldInfo without extend.") + .item_name + .as_ref() + .map_or_else( + || self.name.to_string().trim_start().trim_start_matches("r#").to_string() + "_item", + |item_name| item_name.to_string(), + ) + } + pub fn type_ident(&self) -> syn::Type { ident_to_type(self.generic_ident.clone()) } - pub fn tuplized_type_ty_param(&self) -> syn::Type { + pub fn tuplized_type_ty_param(&self) -> Result { let mut types = syn::punctuated::Punctuated::default(); - types.push(self.ty.clone()); + types.push( + if matches!( + self.builder_attr.setter, + SetterSettings { + extend: Some(_), + strip_option: Some(_), + .. + } + ) { + self.type_from_inside_option()? + } else { + self.ty + } + .clone(), + ); types.push_punct(Default::default()); - syn::TypeTuple { + Ok(syn::TypeTuple { paren_token: Default::default(), elems: types, } - .into() + .into()) } - pub fn type_from_inside_option(&self) -> Option<&syn::Type> { - let path = if let syn::Type::Path(type_path) = self.ty { - if type_path.qself.is_some() { + pub fn type_from_inside_option(&self) -> Result<&syn::Type, Error> { + assert!(self.builder_attr.setter.strip_option.is_some()); + + pub fn try_<'a>(field_info: &'a FieldInfo) -> Option<&'a syn::Type> { + let path = if let syn::Type::Path(type_path) = field_info.ty { + if type_path.qself.is_some() { + return None; + } + &type_path.path + } else { + return None; + }; + let segment = path.segments.last()?; + if segment.ident != "Option" { return None; } - &type_path.path - } else { - return None; - }; - let segment = path.segments.last()?; - if segment.ident != "Option" { - return None; - } - let generic_params = if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments { - generic_params - } else { - return None; - }; - if let syn::GenericArgument::Type(ty) = generic_params.args.first()? { - Some(ty) - } else { - None + let generic_params = if let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments { + generic_params + } else { + return None; + }; + if let syn::GenericArgument::Type(ty) = generic_params.args.first()? { + Some(ty) + } else { + None + } } + + try_(self).ok_or_else(|| Error::new_spanned(&self.ty, "can't `strip_option` - field is not `Option<...>`")) } fn post_process(mut self) -> Result { @@ -107,11 +141,54 @@ pub struct SetterSettings { pub doc: Option, pub skip: Option, pub auto_into: Option, + pub extend: Option, pub strip_option: Option, pub strip_bool: Option, pub transform: Option, } +#[derive(Debug, Clone)] +pub enum Configurable { + Unset, + Auto { keyword_span: Span }, + Custom { keyword_span: Span, value: T }, +} + +#[derive(Debug, Clone)] +pub struct Configured { + pub keyword_span: Span, + pub value: T, +} + +impl Configurable { + pub fn into_configured(self, auto: impl FnOnce(Span) -> T) -> Option> { + match self { + Configurable::Unset => None, + Configurable::Auto { keyword_span } => Some(Configured { + keyword_span, + value: auto(keyword_span), + }), + Configurable::Custom { keyword_span, value } => Some(Configured { keyword_span, value }), + } + } + + pub fn ensure_unset(&self, error_span: Span) -> Result<(), Error> { + if matches!(self, Configurable::Unset) { + Ok(()) + } else { + Err(Error::new(error_span, "Duplicate option")) + } + } +} + +#[derive(Debug, Clone)] +pub struct ExtendField { + pub keyword_span: Span, + pub from_first: Configurable, + pub from_iter: Configurable, + pub item_name: Option, +} + impl FieldBuilderAttr { pub fn with(mut self, attrs: &[syn::Attribute]) -> Result { for attr in attrs { @@ -284,6 +361,110 @@ impl SetterSettings { _ => Err(Error::new_spanned(&assign, format!("Unknown parameter {:?}", name))), } } + syn::Expr::Call(call) => { + let name = + expr_to_single_string(&call.func).ok_or_else(|| Error::new_spanned(&call.func, "Expected identifier"))?; + match name.as_str() { + "extend" => { + if self.extend.is_some() { + Err(Error::new( + call.span(), + "Illegal setting - field is already calling extend(...) with the argument", + )) + } else if let Some(attr) = call.attrs.first() { + Err(Error::new_spanned(attr, "Unexpected attribute")) + } else { + let mut extend = ExtendField { + keyword_span: name.span(), + from_first: Configurable::Unset, + from_iter: Configurable::Unset, + item_name: None, + }; + for arg in call.args { + match arg { + syn::Expr::Assign(assign) => { + let name = expr_to_single_string(&assign.left) + .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?; + match name.as_str() { + "from_first" => { + extend.from_first.ensure_unset(assign.left.span())?; + match *assign.right { + syn::Expr::Closure(closure) => { + extend.from_first = Configurable::Custom { + keyword_span: assign.left.span(), + value: closure, + } + } + other => { + return Err(Error::new_spanned(other, "Expected closure (|first| <...>)")) + } + } + } + "from_iter" => { + extend.from_iter.ensure_unset(assign.left.span())?; + match *assign.right { + syn::Expr::Closure(closure) => { + extend.from_iter = Configurable::Custom { + keyword_span: assign.left.span(), + value: closure, + } + } + other => { + return Err(Error::new_spanned(other, "Expected closure (|iter| <...>)")) + } + } + } + "item_name" => { + if extend.item_name.is_some() { + return Err(Error::new_spanned(assign.left, "Duplicate option")); + } + match *assign.right { + syn::Expr::Path(path) => { + let name = path_to_single_ident(&path.path) + .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; + extend.item_name = Some(name.clone()) + } + other => return Err(Error::new_spanned(other, "Expected identifier")), + } + } + _ => { + return Err(Error::new_spanned( + &assign.left, + format!("Unknown parameter {:?}", name), + )) + } + } + } + syn::Expr::Path(path) => { + let name = path_to_single_string(&path.path) + .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; + match name.as_str() { + "from_first" => { + extend.from_first.ensure_unset(path.span())?; + extend.from_first = Configurable::Auto { + keyword_span: path.span(), + }; + } + "from_iter" => { + extend.from_iter.ensure_unset(path.span())?; + extend.from_iter = Configurable::Auto { + keyword_span: path.span(), + }; + } + "item_name" => return Err(Error::new_spanned(path, "Expected (item_name = <...>)")), + _ => return Err(Error::new_spanned(path, format!("Unknown parameter {:?}", name))), + } + } + _ => return Err(Error::new_spanned(arg, "Expected (<...>) or (<...>=<...>)")), + } + } + self.extend = Some(extend); + Ok(()) + } + } + _ => Err(Error::new_spanned(&call.func, format!("Unknown parameter {:?}", name))), + } + } syn::Expr::Path(path) => { let name = path_to_single_string(&path.path).ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?; macro_rules! handle_fields { @@ -300,6 +481,19 @@ impl SetterSettings { } } )* + "extend" => { + if self.extend.is_some() { + Err(Error::new(path.span(), "Illegal setting - field is already calling extend(...) with the argument")) + } else { + self.extend = Some(ExtendField { + keyword_span: name.span(), + from_first: Configurable::Auto { keyword_span:name.span() }, + from_iter: Configurable::Auto { keyword_span:name.span() }, + item_name: None, + }); + Ok(()) + } + } _ => Err(Error::new_spanned( &path, format!("Unknown setter parameter {:?}", name), @@ -335,6 +529,10 @@ impl SetterSettings { self.auto_into = None; Ok(()) } + "extend" => { + self.extend = None; + Ok(()) + } "strip_option" => { self.strip_option = None; Ok(()) @@ -349,7 +547,7 @@ impl SetterSettings { Err(Error::new_spanned(expr, "Expected simple identifier".to_owned())) } } - _ => Err(Error::new_spanned(expr, "Expected (<...>=<...>)")), + _ => Err(Error::new_spanned(expr, "Expected (<...>=<...>) or (<...>(<...>))")), } } } diff --git a/src/lib.rs b/src/lib.rs index 7367d718..773543b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,8 +181,8 @@ fn impl_my_derive(ast: &syn::DeriveInput) -> Result { .included_fields() .filter(|f| f.builder_attr.default.is_none()) .map(|f| struct_info.required_field_impl(f)) - .collect::>(); - let build_method = struct_info.build_method_impl(); + .collect::, _>>()?; + let build_method = struct_info.build_method_impl()?; quote! { #builder_creation diff --git a/src/struct_info.rs b/src/struct_info.rs index 9c43d61c..8e7c5716 100644 --- a/src/struct_info.rs +++ b/src/struct_info.rs @@ -1,11 +1,12 @@ -use proc_macro2::TokenStream; -use quote::quote; -use syn::parse::Error; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; +use std::fmt::Write; +use syn::{parse::Error, spanned::Spanned}; -use crate::field_info::{FieldBuilderAttr, FieldInfo}; +use crate::field_info::{Configured, ExtendField, FieldBuilderAttr, FieldInfo, SetterSettings}; use crate::util::{ - empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single, modify_types_generics_hack, - path_to_single_string, strip_raw_ident_prefix, type_tuple, + empty_type, empty_type_tuple, expr_to_single_string, make_punctuated_single, path_to_single_string, strip_raw_ident_prefix, + try_modify_types_generics_hack, type_tuple, }; #[derive(Debug)] @@ -50,6 +51,15 @@ impl<'a> StructInfo<'a> { generics } + fn try_modify_generics Result<(), Error>>( + &self, + mut mutator: F, + ) -> Result { + let mut generics = self.generics.clone(); + mutator(&mut generics)?; + Ok(generics) + } + pub fn builder_creation_impl(&self) -> Result { let StructInfo { ref vis, @@ -64,9 +74,10 @@ impl<'a> StructInfo<'a> { g.params.insert(0, all_fields_param.clone()); }); let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type())); - let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { + let generics_with_empty = try_modify_types_generics_hack(&ty_generics, |args| { args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into())); - }); + Ok(()) + })?; let phantom_generics = self.generics.params.iter().map(|param| match param { syn::GenericParam::Lifetime(lifetime) => { let lifetime = &lifetime.lifetime; @@ -173,18 +184,26 @@ impl<'a> StructInfo<'a> { #[allow(dead_code, non_camel_case_types, non_snake_case)] pub trait #trait_name { fn into_value T>(self, default: F) -> T; + fn into_some_or_else ::core::option::Option>(self, default: F) -> ::core::option::Option; } impl #trait_name for () { fn into_value T>(self, default: F) -> T { default() } + fn into_some_or_else ::core::option::Option>(self, default: F) -> ::core::option::Option { + default() + } } impl #trait_name for (T,) { fn into_value T>(self, _: F) -> T { self.0 } + + fn into_some_or_else ::core::option::Option>(self, _: F) -> ::core::option::Option { + ::core::option::Option::Some(self.0) + } } } } @@ -225,7 +244,7 @@ impl<'a> StructInfo<'a> { .collect(); let mut target_generics_tuple = empty_type_tuple(); let mut ty_generics_tuple = empty_type_tuple(); - let generics = self.modify_generics(|g| { + let generics = self.try_modify_generics(|g| { let index_after_lifetime_in_generics = g .params .iter() @@ -234,7 +253,7 @@ impl<'a> StructInfo<'a> { for f in self.included_fields() { if f.ordinal == field.ordinal { ty_generics_tuple.elems.push_value(empty_type()); - target_generics_tuple.elems.push_value(f.tuplized_type_ty_param()); + target_generics_tuple.elems.push_value(f.tuplized_type_ty_param()?); } else { g.params.insert(index_after_lifetime_in_generics, f.generic_ty_param()); let generic_argument: syn::Type = f.type_ident(); @@ -244,7 +263,8 @@ impl<'a> StructInfo<'a> { ty_generics_tuple.elems.push_punct(Default::default()); target_generics_tuple.elems.push_punct(Default::default()); } - }); + Ok(()) + })?; let mut target_generics = ty_generics.clone(); let index_after_lifetime_in_generics = target_generics .iter() @@ -264,74 +284,257 @@ impl<'a> StructInfo<'a> { None => quote!(), }; - // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of - // nesting is different so we have to do this little dance. - let arg_type = if field.builder_attr.setter.strip_option.is_some() && field.builder_attr.setter.transform.is_none() { - let internal_type = field - .type_from_inside_option() - .ok_or_else(|| Error::new_spanned(&field_type, "can't `strip_option` - field is not `Option<...>`"))?; - internal_type - } else { - field_type - }; - let (arg_type, arg_expr) = if field.builder_attr.setter.auto_into.is_some() { - (quote!(impl ::core::convert::Into<#arg_type>), quote!(#field_name.into())) - } else { - (quote!(#arg_type), quote!(#field_name)) - }; + if let Some(ExtendField { + keyword_span, + from_first, + from_iter, + item_name: _, + }) = &field.builder_attr.setter.extend + { + // Changing the builder field type entirely here (instead of just the argument) means there + // won't be a need to unwrap an `Option` in every repeat field setter call. + let field_type = if field.builder_attr.setter.strip_option.is_some() { + let internal_type = field.type_from_inside_option()?; + internal_type + } else { + field_type + }; + + let item_name = syn::Ident::new(&field.item_name(), field.name.span()); + + let descructuring = descructuring.collect::>(); + let reconstructing = reconstructing.collect::>(); + + let from_first = from_first + .clone() + .into_configured(|span| { + syn::parse2(quote_spanned! {span.resolved_at(Span::mixed_site())=> + |first| ::core::iter::once(first).collect() + }) + .expect("extend from_first collect") + }) + .map({ + |Configured { keyword_span, value }| { + quote_spanned! {keyword_span.resolved_at(Span::mixed_site())=> + #doc + pub fn #item_name(self, #item_name: <#field_type as ::core::iter::IntoIterator>::Item) -> #builder_name<#(#target_generics),*> { + let from_item = #value; + let _: &dyn FnOnce(<#field_type as ::core::iter::IntoIterator>::Item) -> #field_type = &from_item; + let #field_name: (#field_type,) = ((from_item)(#item_name),); + let ( #(#descructuring,)* ) = self.fields; + #builder_name { + fields: ( #(#reconstructing,)* ), + phantom: self.phantom, + } + } + } + }}); + let from_iter = from_iter.clone() + .into_configured(|span| { + syn::parse2(quote_spanned!(span.resolved_at(Span::mixed_site())=> |iter| iter.collect())) + .expect("extend from_iter collect") + }).map(|Configured { keyword_span, value }| { + // A plain closure like `|iter| iter.collect()` would require a generic type annotation on the parameter type, + // which unfortunately isn't possible. This means the closure is repackaged as nested function. + let syn::ExprClosure { + attrs, + asyncness, + movability, + capture: _, // No locals are in scope here, so this doesn't matter. + or1_token, + inputs, + or2_token, + output, + body, + } = value; + + // A few error messages can be made much more direct on the way: + + if let Some(asyncness) = asyncness { + return Err(Error::new_spanned(asyncness, "Expected synchronous closure (Remove `async`)")) + } - let (param_list, arg_expr) = if field.builder_attr.setter.strip_bool.is_some() { - (quote!(), quote!(true)) - } else if let Some(transform) = &field.builder_attr.setter.transform { - let params = transform.params.iter().map(|(pat, ty)| quote!(#pat: #ty)); - let body = &transform.body; - (quote!(#(#params),*), quote!({ #body })) - } else if field.builder_attr.setter.strip_option.is_some() { - (quote!(#field_name: #arg_type), quote!(Some(#arg_expr))) - } else { - (quote!(#field_name: #arg_type), arg_expr) - }; + if let Some(movability) = movability { + return Err(Error::new_spanned(movability, "Expected movable closure (Remove `static`)")) + } - let repeated_fields_error_type_name = syn::Ident::new( - &format!( - "{}_Error_Repeated_field_{}", - builder_name, - strip_raw_ident_prefix(field_name.to_string()) - ), - proc_macro2::Span::call_site(), - ); - let repeated_fields_error_message = format!("Repeated field {}", field_name); + let input = match inputs.len() { + 0 => return Err(Error::new_spanned(quote!(#or1_token #or2_token), "Expected one argument")), + 1 => inputs.into_iter().next().unwrap(), + _ => return Err(Error::new_spanned(inputs, "Expected only one argument")), + }; - Ok(quote! { - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause { - #doc - pub fn #field_name (self, #param_list) -> #builder_name < #( #target_generics ),* > { - let #field_name = (#arg_expr,); - let ( #(#descructuring,)* ) = self.fields; - #builder_name { - fields: ( #(#reconstructing,)* ), - phantom: self.phantom, + if let syn::Pat::Type(syn::PatType { colon_token, ty, .. }) = input { + return Err(Error::new_spanned(quote!(#colon_token #ty), "Did not expect argument type, since it is set explicitly by the macro")) + } + + // This is a bit roundabout. In short: + // Iff the closure has an explicit return type, and this return type mismatches the builder field type, + // then the "mismatched types" error is located on that explicit closure type rather than on `from_iter`. + let body = match output { + syn::ReturnType::Default => { + // Transforming this like below would bread the `unused_braces` warning on the closure body. + body.into_token_stream() } + syn::ReturnType::Type(arrow, ty) => { + let colon = syn::Token![:](arrow.span()); + quote_spanned!{ty.span()=> + let output#colon #ty = #body; + output + } + } + }; + + let generics = self.modify_generics(|g| { + g.params.push(syn::parse2(quote_spanned!{keyword_span.resolved_at(Span::mixed_site())=> + __x: ::core::iter::Iterator::Item> + }).expect("extend from_iter __x")) + }); + let where_clause = generics.where_clause.as_ref(); + let generics_for_function_generics = self.modify_generics(|g| { + g.params.push(syn::GenericParam::Type(syn::TypeParam { + attrs: vec![], + ident: syn::Ident::new("_", keyword_span.resolved_at(Span::mixed_site())), + colon_token: None, + bounds: syn::punctuated::Punctuated::default(), + eq_token: None, + default: None, + })) + }); + let (_, type_generics, _) = generics_for_function_generics.split_for_impl(); + let type_generics = type_generics.into_token_stream(); + let function_generics = if type_generics.is_empty() { + type_generics + } else { + quote_spanned!(keyword_span.resolved_at(Span::mixed_site())=> ::#type_generics) + }; + + Ok(quote_spanned! {keyword_span.resolved_at(Span::mixed_site())=> + #doc + pub fn #field_name(self, #field_name: impl ::core::iter::IntoIterator::Item>) -> #builder_name<#(#target_generics),*> { + #(#attrs)* + #asyncness fn from_iter#generics(#input: __x) -> #field_type #where_clause { + #body + } + + let #field_name: (#field_type,) = (from_iter#function_generics(#field_name.into_iter()),); + let ( #(#descructuring,)* ) = self.fields; + #builder_name { + fields: ( #(#reconstructing,)* ), + phantom: self.phantom, + } + } + }) + }) + .transpose()?; + + Ok(quote_spanned! {keyword_span.resolved_at(Span::mixed_site())=> + #[allow(non_camel_case_types, missing_docs)] + impl #impl_generics #builder_name<#(#ty_generics),*> #where_clause { + #from_first + #from_iter } - } - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub enum #repeated_fields_error_type_name {} - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl #impl_generics #builder_name < #( #target_generics ),* > #where_clause { - #[deprecated( - note = #repeated_fields_error_message - )] - pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > { - self + + impl #impl_generics #builder_name<#(#target_generics),*> #where_clause { + #doc + pub fn #item_name(self, #item_name: Item) -> #builder_name<#(#target_generics),*> + where + #field_type: ::core::iter::Extend, + { + let ( #(#reconstructing,)* ) = self.fields; + let mut #field_name = #field_name; + ::core::iter::Extend::extend(&mut #field_name.0, ::core::iter::once(#item_name)); + #builder_name { + fields: ( #(#reconstructing,)* ), + phantom: self.phantom, + } + } + + #doc + pub fn #field_name(self, #field_name: Iter) -> #builder_name<#(#target_generics),*> + where + Iter: ::core::iter::IntoIterator, + #field_type: ::core::iter::Extend, + { + let items = #field_name; + let ( #(#reconstructing,)* ) = self.fields; + let mut #field_name = #field_name; + ::core::iter::Extend::extend(&mut #field_name.0, items); + #builder_name { + fields: ( #(#reconstructing,)* ), + phantom: self.phantom, + } + } } - } - }) + }) + } else { + //Not `extend`. + + // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of + // nesting is different so we have to do this little dance. + let arg_type = if field.builder_attr.setter.strip_option.is_some() && field.builder_attr.setter.transform.is_none() { + field.type_from_inside_option()? + } else { + field_type + }; + let (arg_type, arg_expr) = if field.builder_attr.setter.auto_into.is_some() { + (quote!(impl ::core::convert::Into<#arg_type>), quote!(#field_name.into())) + } else { + (quote!(#arg_type), quote!(#field_name)) + }; + let (field, arg_expr) = if field.builder_attr.setter.strip_bool.is_some() { + (quote!(#field_name), quote!(true)) + } else if let Some(transform) = &field.builder_attr.setter.transform { + let params = transform.params.iter().map(|(pat, ty)| quote!(#pat: #ty)); + let body = &transform.body; + (quote!(#(#params),*), quote!({ #body })) + } else if field.builder_attr.setter.strip_option.is_some() { + (quote!(#field_name: (#arg_type,)), quote!(Some(#arg_expr))) + } else { + (quote!(#field_name: (#arg_type,)), arg_expr) + }; + + let repeated_fields_error_type_name = syn::Ident::new( + &format!( + "{}_Error_Repeated_field_{}", + builder_name, + strip_raw_ident_prefix(field_name.to_string()) + ), + proc_macro2::Span::call_site(), + ); + let repeated_fields_error_message = format!("Repeated field {}", field_name); + + Ok(quote! { + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause { + #doc + pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > { + let #field = (#arg_expr,); + let ( #(#descructuring,)* ) = self.fields; + #builder_name { + fields: ( #(#reconstructing,)* ), + phantom: self.phantom, + } + } + } + #[doc(hidden)] + #[allow(dead_code, non_camel_case_types, non_snake_case)] + pub enum #repeated_fields_error_type_name {} + #[doc(hidden)] + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl #impl_generics #builder_name < #( #target_generics ),* > #where_clause { + #[deprecated( + note = #repeated_fields_error_message + )] + pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > { + self + } + } + }) + } } - pub fn required_field_impl(&self, field: &FieldInfo) -> TokenStream { + pub fn required_field_impl(&self, field: &FieldInfo) -> Result { let StructInfo { ref name, ref builder_name, @@ -358,7 +561,7 @@ impl<'a> StructInfo<'a> { }) .collect(); let mut builder_generics_tuple = empty_type_tuple(); - let generics = self.modify_generics(|g| { + let generics = self.try_modify_generics(|g| { let index_after_lifetime_in_generics = g .params .iter() @@ -378,7 +581,7 @@ impl<'a> StructInfo<'a> { } else if f.ordinal < field.ordinal { // Only add a `build` method that warns about missing `field` if `f` is set. If `f` is not set, // `f`'s `build` method will warn, since it appears earlier in the argument list. - builder_generics_tuple.elems.push_value(f.tuplized_type_ty_param()); + builder_generics_tuple.elems.push_value(f.tuplized_type_ty_param()?); } else if f.ordinal == field.ordinal { builder_generics_tuple.elems.push_value(empty_type()); } else { @@ -391,7 +594,8 @@ impl<'a> StructInfo<'a> { builder_generics_tuple.elems.push_punct(Default::default()); } - }); + Ok(()) + })?; let index_after_lifetime_in_generics = builder_generics .iter() @@ -412,9 +616,12 @@ impl<'a> StructInfo<'a> { ), proc_macro2::Span::call_site(), ); - let early_build_error_message = format!("Missing required field {}", field_name); + let mut early_build_error_message = format!("Missing required field {}", field_name); + if field.builder_attr.setter.extend.is_some() { + write!(&mut early_build_error_message, " (single item setter: {})", field.item_name()).unwrap(); + } - quote! { + Ok(quote! { #[doc(hidden)] #[allow(dead_code, non_camel_case_types, non_snake_case)] pub enum #early_build_error_type_name {} @@ -428,23 +635,32 @@ impl<'a> StructInfo<'a> { panic!(); } } - } + }) } - pub fn build_method_impl(&self) -> TokenStream { + pub fn build_method_impl(&self) -> Result { let StructInfo { ref name, ref builder_name, .. } = *self; - let generics = self.modify_generics(|g| { + let generics = self.try_modify_generics(|g| { let index_after_lifetime_in_generics = g .params .iter() .filter(|arg| matches!(arg, syn::GenericParam::Lifetime(_))) .count(); for field in self.included_fields() { + let strip_option_extends = matches!( + field.builder_attr.setter, + SetterSettings { + extend: Some(_), + strip_option: Some(_), + .. + } + ); + if field.builder_attr.default.is_some() { let trait_ref = syn::TraitBound { paren_token: None, @@ -455,7 +671,11 @@ impl<'a> StructInfo<'a> { arguments: syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { colon2_token: None, lt_token: Default::default(), - args: make_punctuated_single(syn::GenericArgument::Type(field.ty.clone())), + args: make_punctuated_single(syn::GenericArgument::Type(if strip_option_extends { + field.type_from_inside_option()?.clone() + } else { + field.ty.clone() + })), gt_token: Default::default(), }), } @@ -466,26 +686,33 @@ impl<'a> StructInfo<'a> { g.params.insert(index_after_lifetime_in_generics, generic_param.into()); } } - }); + Ok(()) + })?; let (impl_generics, _, _) = generics.split_for_impl(); let (_, ty_generics, where_clause) = self.generics.split_for_impl(); - let modified_ty_generics = modify_types_generics_hack(&ty_generics, |args| { + let modified_ty_generics = try_modify_types_generics_hack(&ty_generics, |args| { args.insert( 0, syn::GenericArgument::Type( - type_tuple(self.included_fields().map(|field| { - if field.builder_attr.default.is_some() { - field.type_ident() - } else { - field.tuplized_type_ty_param() - } - })) + type_tuple( + self.included_fields() + .map(|field| { + if field.builder_attr.default.is_some() { + Ok(field.type_ident()) + } else { + field.tuplized_type_ty_param() + } + }) + .collect::, _>>()? + .into_iter(), + ) .into(), ), ); - }); + Ok(()) + })?; let descructuring = self.included_fields().map(|f| f.name); @@ -496,13 +723,25 @@ impl<'a> StructInfo<'a> { // relax that restriction by calculating a DAG of field default dependencies and // reordering based on that, but for now this much simpler thing is a reasonable approach. let assignments = self.fields.iter().map(|field| { - let name = &field.name; + let name = field.name; + let wrap_some = matches!( + field.builder_attr.setter, + SetterSettings { + extend: Some(_), + strip_option: Some(_), + .. + } + ); if let Some(ref default) = field.builder_attr.default { if field.builder_attr.setter.skip.is_some() { quote!(let #name = #default;) + } else if wrap_some { + quote!(let #name = #helper_trait_name::into_some_or_else(#name, || #default);) } else { quote!(let #name = #helper_trait_name::into_value(#name, || #default);) } + } else if wrap_some { + quote!(let #name = ::core::option::Option::Some(#name.0);) } else { quote!(let #name = #name.0;) } @@ -535,7 +774,7 @@ impl<'a> StructInfo<'a> { .as_ref() .map(|v| quote!(#v)) .unwrap_or(quote!(pub)); - quote!( + Ok(quote!( #[allow(dead_code, non_camel_case_types, missing_docs)] impl #impl_generics #builder_name #modified_ty_generics #where_clause { #doc @@ -548,7 +787,7 @@ impl<'a> StructInfo<'a> { } } } - ) + )) } } diff --git a/src/util.rs b/src/util.rs index ae3306d8..1171bbea 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,7 @@ use quote::ToTokens; +use syn::Error; -pub fn path_to_single_string(path: &syn::Path) -> Option { +pub fn path_to_single_ident(path: &syn::Path) -> Option<&syn::Ident> { if path.leading_colon.is_some() { return None; } @@ -13,7 +14,11 @@ pub fn path_to_single_string(path: &syn::Path) -> Option { if segment.arguments != syn::PathArguments::None { return None; } - Some(segment.ident.to_string()) + Some(&segment.ident) +} + +pub fn path_to_single_string(path: &syn::Path) -> Option { + path_to_single_ident(path).map(|i| i.to_string()) } pub fn expr_to_single_string(expr: &syn::Expr) -> Option { @@ -68,9 +73,12 @@ pub fn make_punctuated_single(value: T) -> syn::punctuated::Punct punctuated } -pub fn modify_types_generics_hack(ty_generics: &syn::TypeGenerics, mut mutator: F) -> syn::AngleBracketedGenericArguments +pub fn try_modify_types_generics_hack( + ty_generics: &syn::TypeGenerics, + mut mutator: F, +) -> Result where - F: FnMut(&mut syn::punctuated::Punctuated), + F: FnMut(&mut syn::punctuated::Punctuated) -> Result<(), Error>, { let mut abga: syn::AngleBracketedGenericArguments = syn::parse(ty_generics.clone().into_token_stream().into()) .unwrap_or_else(|_| syn::AngleBracketedGenericArguments { @@ -79,8 +87,8 @@ where args: Default::default(), gt_token: Default::default(), }); - mutator(&mut abga.args); - abga + mutator(&mut abga.args)?; + Ok(abga) } pub fn strip_raw_ident_prefix(mut name: String) -> String { diff --git a/tests/extend.rs b/tests/extend.rs new file mode 100644 index 00000000..d9b0c215 --- /dev/null +++ b/tests/extend.rs @@ -0,0 +1,137 @@ +use typed_builder::TypedBuilder; + +#[test] +fn simple_vec() { + #[derive(TypedBuilder)] + struct A { + #[builder(setter(extend))] + v: Vec, + } + + assert_eq!(A::builder().v_item(2).build().v, vec![2]); + assert_eq!(A::builder().v(vec![3, 4]).build().v, vec![3, 4]); + assert_eq!(A::builder().v_item(5).v_item(6).build().v, vec![5, 6]); + assert_eq!(A::builder().v(vec![7, 8]).v_item(9).build().v, vec![7, 8, 9]); + assert_eq!(A::builder().v_item(0).v(vec![1, 2]).build().v, vec![0, 1, 2]); + assert_eq!(A::builder().v(vec![3, 4]).v(vec![5, 6]).build().v, vec![3, 4, 5, 6]); +} + +#[test] +fn item_name() { + #[derive(TypedBuilder)] + struct A { + #[builder(setter(extend(from_first, from_iter, item_name = i)))] + v: Vec, + } + + assert_eq!(A::builder().i(2).build().v, vec![2]); + assert_eq!(A::builder().i(5).i(6).build().v, vec![5, 6]); + assert_eq!(A::builder().v(vec![7, 8]).i(9).build().v, vec![7, 8, 9]); + assert_eq!(A::builder().i(0).v(vec![1, 2]).build().v, vec![0, 1, 2]); +} + +#[test] +fn default_and_implicit_initializers() { + #[derive(TypedBuilder)] + struct A { + #[builder(default = vec![0], setter(extend))] + v: Vec, + } + + assert_eq!(A::builder().build().v, vec![0]); + assert_eq!(A::builder().v_item(2).build().v, vec![2]); + assert_eq!(A::builder().v(vec![3, 4]).build().v, vec![3, 4]); +} + +#[test] +fn default_and_explicit_auto_initializers() { + #[derive(TypedBuilder)] + struct A { + #[builder(default = vec![0], setter(extend(from_first, from_iter)))] + v: Vec, + } + + assert_eq!(A::builder().build().v, vec![0]); + assert_eq!(A::builder().v_item(2).build().v, vec![2]); + assert_eq!(A::builder().v(vec![3, 4]).build().v, vec![3, 4]); +} + +#[test] +fn default_and_explicit_initializer_closures() { + #[derive(TypedBuilder)] + struct A { + #[builder(default = vec![0], setter(extend(from_first = |first| vec![first], from_iter = |iter| iter.collect())))] + v: Vec, + } + + assert_eq!(A::builder().build().v, vec![0]); + assert_eq!(A::builder().v_item(2).build().v, vec![2]); + assert_eq!(A::builder().v(vec![3, 4]).build().v, vec![3, 4]); +} + +#[test] +fn generic_inference() { + #[derive(TypedBuilder)] + struct A { + #[builder(setter(extend))] + v: Vec, + } + + #[derive(TypedBuilder)] + struct B { + #[builder(setter(extend))] + s: Vec, + #[builder(setter(extend))] + t: Vec, + } + + let A { v: _v } = A::builder().v(vec![true]).build(); + let _ = A::builder().v_item(0).build(); + + let B { s: _s, t: _t } = B::builder().s(vec![true]).t(vec![false]).build(); + let _ = B::builder().s(vec![0]).t_item(1).build(); + let _ = B::builder().s_item('a').t(vec![false]).build(); + let _ = B::builder().s_item("b").t_item(1).build(); +} + +#[test] +fn strip_option() { + #[derive(TypedBuilder)] + struct A { + #[builder(default, setter(strip_option, extend))] + v: Option>, + } + + assert_eq!(A::builder().build().v, None); + assert_eq!(A::builder().v_item(2).build().v, Some(vec![2])); + assert_eq!(A::builder().v(vec![3, 4]).build().v, Some(vec![3, 4])); + assert_eq!(A::builder().v_item(5).v_item(6).build().v, Some(vec![5, 6])); + assert_eq!(A::builder().v(vec![7, 8]).v_item(9).build().v, Some(vec![7, 8, 9])); + assert_eq!(A::builder().v_item(0).v(vec![1, 2]).build().v, Some(vec![0, 1, 2])); + assert_eq!(A::builder().v(vec![3, 4]).v(vec![5, 6]).build().v, Some(vec![3, 4, 5, 6])); +} + +#[test] +fn strip_option_generic_inference() { + #[derive(TypedBuilder)] + struct A { + #[builder(default, setter(strip_option, extend))] + v: Option>, + } + + #[derive(TypedBuilder)] + struct B { + #[builder(default, setter(strip_option, extend))] + s: Option>, + #[builder(default, setter(strip_option, extend))] + t: Option>, + } + + let A { v: _v } = A::builder().v(vec![true]).build(); + let _ = A::builder().v_item(0).build(); + + let B { s: _s, t: _t } = B::builder().s(vec![true]).t(vec![false]).build(); + let _ = B::builder().s(vec![0]).t_item(1).build(); + let _ = B::builder().s_item('a').t(vec![false]).build(); + let _ = B::builder().s_item("b").t_item(1).build(); +}