From 3c4aed3a57c0cb1c9f6fce35eb78f06949fa14d6 Mon Sep 17 00:00:00 2001 From: treysidechain Date: Tue, 6 Dec 2022 18:13:34 -0800 Subject: [PATCH 01/11] add support for initializing builder's generic parameters with default values if the field types using those generic params impl Default --- Cargo.toml | 5 + examples/default_generics.rs | 17 + src/field_info.rs | 59 +- src/lib.rs | 9 +- src/struct_info.rs | 138 +++-- src/util.rs | 12 +- tests/tests.rs | 1065 +++++++++++++++++----------------- 7 files changed, 740 insertions(+), 565 deletions(-) create mode 100644 examples/default_generics.rs diff --git a/Cargo.toml b/Cargo.toml index a2b9da3d..b750fe2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,8 @@ proc-macro = true syn = { version = "1", features = ["full", "extra-traits"] } quote = "1" proc-macro2 = "1" +regex = "1.7.0" +prettyplease = { version = "0.1.21", optional = true } + +[features] +debug = ["dep:prettyplease"] diff --git a/examples/default_generics.rs b/examples/default_generics.rs new file mode 100644 index 00000000..d04df786 --- /dev/null +++ b/examples/default_generics.rs @@ -0,0 +1,17 @@ +use typed_builder::TypedBuilder; + +#[derive(TypedBuilder)] +pub struct Props<'a, OnInput: FnOnce(usize) -> usize = Box usize>> { + #[builder(default, setter(into))] + pub class: Option<&'a str>, + pub label: &'a str, + #[builder(setter(into))] + pub on_input: Option, +} + +fn main() { + let props = Props::builder().label("label").on_input(|x: usize| x).build(); + assert_eq!(props.class, None); + assert_eq!(props.label, "label"); + assert_eq!((props.on_input.unwrap())(123), 123); +} diff --git a/src/field_info.rs b/src/field_info.rs index 1be21a0e..46eb3f42 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::{empty_type, expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, TypeGenericDefaults}; #[derive(Debug)] pub struct FieldInfo<'a> { @@ -12,19 +12,54 @@ pub struct FieldInfo<'a> { pub generic_ident: syn::Ident, pub ty: &'a syn::Type, pub builder_attr: FieldBuilderAttr, + pub default_ty: Option<(syn::Type, syn::Expr)>, } impl<'a> FieldInfo<'a> { - pub fn new(ordinal: usize, field: &syn::Field, field_defaults: FieldBuilderAttr) -> Result { + pub fn new(ordinal: usize, field: &'a syn::Field, field_defaults: FieldBuilderAttr, type_generic_defaults: &TypeGenericDefaults) -> Result { if let Some(ref name) = field.ident { - FieldInfo { + let mut field_info = FieldInfo { ordinal, name, generic_ident: syn::Ident::new(&format!("__{}", strip_raw_ident_prefix(name.to_string())), Span::call_site()), ty: &field.ty, builder_attr: field_defaults.with(&field.attrs)?, + default_ty: None, } - .post_process() + .post_process()?; + + if field_info.builder_attr.default.is_none() { + let mut ty_includes_params_without_defaults = false; + let mut ty_includes_params_with_defaults = false; + let ty = &field.ty; + let mut ty_str = format!("{}", quote! { #ty }); + for (type_param, default_type) in type_generic_defaults.iter() { + if type_param.is_match(&ty_str) { + match default_type.as_ref() { + Some(default_type) => { + ty_includes_params_with_defaults = true; + ty_str = type_param.replace(&ty_str, default_type).into(); + }, + None => { + ty_includes_params_without_defaults = true; + break; + } + } + } + } + if !ty_includes_params_without_defaults && ty_includes_params_with_defaults { + use std::str::FromStr; + let expr_str = format!("(<{ty_str} as Default>::default(),)"); + let ty_str = format!("({ty_str},)"); + field_info.default_ty = Some(( + syn::parse(TokenStream::from_str(&ty_str)?.into())?, + syn::parse(TokenStream::from_str(&expr_str)?.into())?, + )); + } + } + + Ok(field_info) + } else { Err(Error::new(field.span(), "Nameless field in struct")) } @@ -94,6 +129,22 @@ impl<'a> FieldInfo<'a> { } Ok(self) } + + pub fn default_type(&self) -> syn::Type { + if let Some((ty, _)) = self.default_ty.as_ref() { + ty.clone() + } else { + empty_type() + } + } + + pub fn default_expr(&self) -> syn::Expr { + if let Some((_, expr)) = self.default_ty.as_ref() { + expr.clone() + } else { + syn::parse(quote!(()).into()).unwrap() + } + } } #[derive(Debug, Default, Clone)] diff --git a/src/lib.rs b/src/lib.rs index 7367d718..ffd08d66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,10 +159,13 @@ mod util; #[proc_macro_derive(TypedBuilder, attributes(builder))] pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); - match impl_my_derive(&input) { + let tokens: proc_macro::TokenStream = match impl_my_derive(&input) { Ok(output) => output.into(), Err(error) => error.to_compile_error().into(), - } + }; + #[cfg(feature = "debug")] + println!("{}", prettyplease::unparse(&syn::parse_file(&format!("{}", proc_macro2::TokenStream::from(tokens.clone()))).unwrap())); + tokens } fn impl_my_derive(ast: &syn::DeriveInput) -> Result { @@ -179,7 +182,7 @@ fn impl_my_derive(ast: &syn::DeriveInput) -> Result { let fields = quote!(#(#fields)*).into_iter(); let required_fields = struct_info .included_fields() - .filter(|f| f.builder_attr.default.is_none()) + .filter(|f| f.builder_attr.default.is_none() && f.default_ty.is_none()) .map(|f| struct_info.required_field_impl(f)) .collect::>(); let build_method = struct_info.build_method_impl(); diff --git a/src/struct_info.rs b/src/struct_info.rs index 9c43d61c..d6bf36d2 100644 --- a/src/struct_info.rs +++ b/src/struct_info.rs @@ -1,11 +1,12 @@ use proc_macro2::TokenStream; use quote::quote; +use regex::Regex; use syn::parse::Error; use crate::field_info::{FieldBuilderAttr, FieldInfo}; 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_tuple, expr_to_single_string, expr_tuple, make_punctuated_single, modify_types_generics_hack, + path_to_single_string, strip_raw_ident_prefix, TypeGenericDefaults, type_tuple, }; #[derive(Debug)] @@ -15,6 +16,14 @@ pub struct StructInfo<'a> { pub generics: &'a syn::Generics, pub fields: Vec>, + // all generics, just with default types removed + pub generics_without_defaults: syn::Generics, + + // only generics which had no defaults specified + pub no_default_generics: syn::Generics, + + pub type_generic_defaults: TypeGenericDefaults, + pub builder_attr: TypeBuilderAttr, pub builder_name: syn::Ident, pub conversion_helper_trait_name: syn::Ident, @@ -29,18 +38,64 @@ impl<'a> StructInfo<'a> { pub fn new(ast: &'a syn::DeriveInput, fields: impl Iterator) -> Result, Error> { let builder_attr = TypeBuilderAttr::new(&ast.attrs)?; let builder_name = strip_raw_ident_prefix(format!("{}Builder", ast.ident)); + + let mut generics_without_defaults = ast.generics.clone(); + generics_without_defaults.params = generics_without_defaults.params + .into_iter() + .map(|param| match param { + syn::GenericParam::Type(type_param) => syn::GenericParam::Type(syn::TypeParam { + attrs: type_param.attrs, + ident: type_param.ident, + colon_token: type_param.colon_token, + bounds: type_param.bounds, + eq_token: None, + default: None, + }), + param => param, + }) + .collect(); + + let mut no_default_generics = ast.generics.clone(); + let mut type_generic_defaults = TypeGenericDefaults::default(); + no_default_generics.params = no_default_generics.params + .into_iter() + .filter_map(|param| match param { + syn::GenericParam::Type(type_param) => match type_param.default.clone() { + Some(default) => { + let ident = &type_param.ident; + type_generic_defaults.push(( + Regex::new(format!(r#"\b{}\b"#, quote!(#ident)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#type_param)))), + Some(format!("{}", quote!(#default)).trim().to_string()), + )); + None + }, + None => { + type_generic_defaults.push(( + Regex::new(format!(r#"\b{}\b"#, quote!(#type_param)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#type_param)))), + None, + )); + Some(syn::GenericParam::Type(type_param)) + }, + }, + param => Some(param), + }) + .collect(); + Ok(StructInfo { vis: &ast.vis, name: &ast.ident, generics: &ast.generics, fields: fields .enumerate() - .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone())) + .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone(), &type_generic_defaults)) .collect::>()?, - builder_attr, builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()), conversion_helper_trait_name: syn::Ident::new(&format!("{}_Optional", builder_name), proc_macro2::Span::call_site()), core: syn::Ident::new(&format!("{}_core", builder_name), proc_macro2::Span::call_site()), + builder_attr, + generics_without_defaults, + no_default_generics, + type_generic_defaults, }) } @@ -50,6 +105,12 @@ impl<'a> StructInfo<'a> { generics } + fn modify_generics_with_no_defaults(&self, mut mutator: F) -> syn::Generics { + let mut generics = self.generics_without_defaults.clone(); + mutator(&mut generics); + generics + } + pub fn builder_creation_impl(&self) -> Result { let StructInfo { ref vis, @@ -57,15 +118,16 @@ impl<'a> StructInfo<'a> { ref builder_name, .. } = *self; - let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = self.generics_without_defaults.split_for_impl(); let all_fields_param = syn::GenericParam::Type(syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into()); - let b_generics = self.modify_generics(|g| { + let b_generics = self.modify_generics_with_no_defaults(|g| { g.params.insert(0, all_fields_param.clone()); }); - let empties_tuple = type_tuple(self.included_fields().map(|_| empty_type())); + let expr_empties_tuple = expr_tuple(self.included_fields().map(|f| f.default_expr())); + let ty_empties_tuple = type_tuple(self.included_fields().map(|f| f.default_type())); let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { - args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into())); + args.insert(0, syn::GenericArgument::Type(ty_empties_tuple.clone().into())); }); let phantom_generics = self.generics.params.iter().map(|param| match param { syn::GenericParam::Lifetime(lifetime) => { @@ -138,7 +200,7 @@ impl<'a> StructInfo<'a> { #[allow(dead_code, clippy::default_trait_access)] #vis fn builder() -> #builder_name #generics_with_empty { #builder_name { - fields: #empties_tuple, + fields: #expr_empties_tuple, phantom: ::core::default::Default::default(), } } @@ -233,7 +295,7 @@ impl<'a> StructInfo<'a> { .count(); for f in self.included_fields() { if f.ordinal == field.ordinal { - ty_generics_tuple.elems.push_value(empty_type()); + ty_generics_tuple.elems.push_value(f.default_type()); target_generics_tuple.elems.push_value(f.tuplized_type_ty_param()); } else { g.params.insert(index_after_lifetime_in_generics, f.generic_ty_param()); @@ -292,15 +354,36 @@ impl<'a> StructInfo<'a> { (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); + // repeated field impl cannot exist if field includes use of a default type because of overlapping impls + let repeated_field_impl = if field.default_ty.is_none() { + 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); + quote! { + #[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 + } + } + } + } else { + quote! {} + }; Ok(quote! { #[allow(dead_code, non_camel_case_types, missing_docs)] @@ -315,19 +398,8 @@ impl<'a> StructInfo<'a> { } } } - #[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 - } - } + + #repeated_field_impl }) } @@ -380,7 +452,7 @@ impl<'a> StructInfo<'a> { // `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()); } else if f.ordinal == field.ordinal { - builder_generics_tuple.elems.push_value(empty_type()); + builder_generics_tuple.elems.push_value(f.default_type()); } else { // `f` appears later in the argument list after `field`, so if they are both missing we will // show a warning for `field` and not for `f` - which means this warning should appear whether diff --git a/src/util.rs b/src/util.rs index ae3306d8..b2cb3ab1 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,10 @@ -use quote::ToTokens; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use regex::Regex; + +// keys are regex patterns of format /\btype_param\b/ +// values are options of the default type provided, formatted and trimmed as a String +pub type TypeGenericDefaults = Vec<(Regex, Option)>; pub fn path_to_single_string(path: &syn::Path) -> Option { if path.leading_colon.is_some() { @@ -55,6 +61,10 @@ pub fn type_tuple(elems: impl Iterator) -> syn::TypeTuple { result } +pub fn expr_tuple(elems: impl Iterator) -> TokenStream { + quote! { (#(#elems,)*) } +} + pub fn empty_type_tuple() -> syn::TypeTuple { syn::TypeTuple { paren_token: Default::default(), diff --git a/tests/tests.rs b/tests/tests.rs index 0cb43a56..579d9305 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,342 +2,342 @@ use typed_builder::TypedBuilder; -#[test] -fn test_simple() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - x: i32, - y: i32, - } - - assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); - assert!(Foo::builder().y(1).x(2).build() == Foo { x: 2, y: 1 }); -} - -#[test] -fn test_lifetime() { - #[derive(PartialEq, TypedBuilder)] - struct Foo<'a, 'b> { - x: &'a i32, - y: &'b i32, - } - - assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); -} - -#[test] -fn test_lifetime_bounded() { - #[derive(PartialEq, TypedBuilder)] - struct Foo<'a, 'b: 'a> { - x: &'a i32, - y: &'b i32, - } - - assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); -} - -#[test] -fn test_mutable_borrows() { - #[derive(PartialEq, TypedBuilder)] - struct Foo<'a, 'b> { - x: &'a mut i32, - y: &'b mut i32, - } - - let mut a = 1; - let mut b = 2; - { - let foo = Foo::builder().x(&mut a).y(&mut b).build(); - *foo.x *= 10; - *foo.y *= 100; - } - assert!(a == 10); - assert!(b == 200); -} - -#[test] -fn test_generics() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - x: S, - y: T, - } - - assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); -} - -#[test] -fn test_into() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(setter(into))] - x: i32, - } - - assert!(Foo::builder().x(1_u8).build() == Foo { x: 1 }); -} - -#[test] -fn test_strip_option_with_into() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(setter(strip_option, into))] - x: Option, - } - - assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); -} - -#[test] -fn test_into_with_strip_option() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(setter(into, strip_option))] - x: Option, - } - - assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); -} - -#[test] -fn test_strip_bool() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(setter(into, strip_bool))] - x: bool, - } - - assert!(Foo::builder().x().build() == Foo { x: true }); - assert!(Foo::builder().build() == Foo { x: false }); -} - -#[test] -fn test_default() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(default, setter(strip_option))] - x: Option, - #[builder(default = 10)] - y: i32, - #[builder(default = vec![20, 30, 40])] - z: Vec, - } - - assert!( - Foo::builder().build() - == Foo { - x: None, - y: 10, - z: vec![20, 30, 40] - } - ); - assert!( - Foo::builder().x(1).build() - == Foo { - x: Some(1), - y: 10, - z: vec![20, 30, 40] - } - ); - assert!( - Foo::builder().y(2).build() - == Foo { - x: None, - y: 2, - z: vec![20, 30, 40] - } - ); - assert!( - Foo::builder().x(1).y(2).build() - == Foo { - x: Some(1), - y: 2, - z: vec![20, 30, 40] - } - ); - assert!( - Foo::builder().z(vec![1, 2, 3]).build() - == Foo { - x: None, - y: 10, - z: vec![1, 2, 3] - } - ); -} - -#[test] -fn test_field_dependencies_in_build() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(default, setter(strip_option))] - x: Option, - #[builder(default = 10)] - y: i32, - #[builder(default = vec![y, 30, 40])] - z: Vec, - } - - assert!( - Foo::builder().build() - == Foo { - x: None, - y: 10, - z: vec![10, 30, 40] - } - ); - assert!( - Foo::builder().x(1).build() - == Foo { - x: Some(1), - y: 10, - z: vec![10, 30, 40] - } - ); - assert!( - Foo::builder().y(2).build() - == Foo { - x: None, - y: 2, - z: vec![2, 30, 40] - } - ); - assert!( - Foo::builder().x(1).y(2).build() - == Foo { - x: Some(1), - y: 2, - z: vec![2, 30, 40] - } - ); - assert!( - Foo::builder().z(vec![1, 2, 3]).build() - == Foo { - x: None, - y: 10, - z: vec![1, 2, 3] - } - ); -} - -// compile-fail tests for skip are in src/lib.rs out of necessity. These are just the bland -// successful cases. -#[test] -fn test_skip() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(default, setter(skip))] - x: i32, - #[builder(setter(into))] - y: i32, - #[builder(default = y + 1, setter(skip))] - z: i32, - } - - assert!(Foo::builder().y(1_u8).build() == Foo { x: 0, y: 1, z: 2 }); -} - -#[test] -fn test_docs() { - #[derive(TypedBuilder)] - #[builder( - builder_method_doc = "Point::builder() method docs", - builder_type_doc = "PointBuilder type docs", - build_method_doc = "PointBuilder.build() method docs" - )] - struct Point { - #[allow(dead_code)] - x: i32, - #[builder( - default = x, - setter( - doc = "Set `z`. If you don't specify a value it'll default to the value specified for `x`.", - ), - )] - #[allow(dead_code)] - y: i32, - } - - let _ = Point::builder(); -} - -#[test] -fn test_builder_name() { - #[derive(TypedBuilder)] - struct Foo {} - - let _: FooBuilder<_> = Foo::builder(); -} - -// NOTE: `test_builder_type_stability` and `test_builder_type_stability_with_other_generics` are -// meant to ensure we don't break things for people that use custom `impl`s on the builder -// type before the tuple field generic param transformation traits are in. -// See: -// - https://github.com/idanarye/rust-typed-builder/issues/22 -// - https://github.com/idanarye/rust-typed-builder/issues/23 -#[test] -fn test_builder_type_stability() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - x: i32, - y: i32, - z: i32, - } - - impl FooBuilder<((), Y, ())> { - fn xz(self, x: i32, z: i32) -> FooBuilder<((i32,), Y, (i32,))> { - self.x(x).z(z) - } - } - - assert!(Foo::builder().xz(1, 2).y(3).build() == Foo { x: 1, y: 3, z: 2 }); - assert!(Foo::builder().xz(1, 2).y(3).build() == Foo::builder().x(1).z(2).y(3).build()); - - assert!(Foo::builder().y(1).xz(2, 3).build() == Foo { x: 2, y: 1, z: 3 }); - assert!(Foo::builder().y(1).xz(2, 3).build() == Foo::builder().y(1).x(2).z(3).build()); -} - -#[test] -fn test_builder_type_stability_with_other_generics() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - x: X, - y: Y, - } - - impl FooBuilder<((), Y_), X, Y> { - fn x_default(self) -> FooBuilder<((X,), Y_), X, Y> { - self.x(X::default()) - } - } - - assert!(Foo::builder().x_default().y(1.0).build() == Foo { x: 0, y: 1.0 }); - assert!( - Foo::builder().y("hello".to_owned()).x_default().build() - == Foo { - x: "", - y: "hello".to_owned() - } - ); -} +// #[test] +// fn test_simple() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// x: i32, +// y: i32, +// } +// +// assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); +// assert!(Foo::builder().y(1).x(2).build() == Foo { x: 2, y: 1 }); +// } +// +// #[test] +// fn test_lifetime() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo<'a, 'b> { +// x: &'a i32, +// y: &'b i32, +// } +// +// assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); +// } +// +// #[test] +// fn test_lifetime_bounded() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo<'a, 'b: 'a> { +// x: &'a i32, +// y: &'b i32, +// } +// +// assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); +// } +// +// #[test] +// fn test_mutable_borrows() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo<'a, 'b> { +// x: &'a mut i32, +// y: &'b mut i32, +// } +// +// let mut a = 1; +// let mut b = 2; +// { +// let foo = Foo::builder().x(&mut a).y(&mut b).build(); +// *foo.x *= 10; +// *foo.y *= 100; +// } +// assert!(a == 10); +// assert!(b == 200); +// } +// +// #[test] +// fn test_generics() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// x: S, +// y: T, +// } +// +// assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); +// } +// +// #[test] +// fn test_into() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(setter(into))] +// x: i32, +// } +// +// assert!(Foo::builder().x(1_u8).build() == Foo { x: 1 }); +// } +// +// #[test] +// fn test_strip_option_with_into() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(setter(strip_option, into))] +// x: Option, +// } +// +// assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); +// } +// +// #[test] +// fn test_into_with_strip_option() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(setter(into, strip_option))] +// x: Option, +// } +// +// assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); +// } +// +// #[test] +// fn test_strip_bool() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(setter(into, strip_bool))] +// x: bool, +// } +// +// assert!(Foo::builder().x().build() == Foo { x: true }); +// assert!(Foo::builder().build() == Foo { x: false }); +// } +// +// #[test] +// fn test_default() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(default, setter(strip_option))] +// x: Option, +// #[builder(default = 10)] +// y: i32, +// #[builder(default = vec![20, 30, 40])] +// z: Vec, +// } +// +// assert!( +// Foo::builder().build() +// == Foo { +// x: None, +// y: 10, +// z: vec![20, 30, 40] +// } +// ); +// assert!( +// Foo::builder().x(1).build() +// == Foo { +// x: Some(1), +// y: 10, +// z: vec![20, 30, 40] +// } +// ); +// assert!( +// Foo::builder().y(2).build() +// == Foo { +// x: None, +// y: 2, +// z: vec![20, 30, 40] +// } +// ); +// assert!( +// Foo::builder().x(1).y(2).build() +// == Foo { +// x: Some(1), +// y: 2, +// z: vec![20, 30, 40] +// } +// ); +// assert!( +// Foo::builder().z(vec![1, 2, 3]).build() +// == Foo { +// x: None, +// y: 10, +// z: vec![1, 2, 3] +// } +// ); +// } +// +// #[test] +// fn test_field_dependencies_in_build() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(default, setter(strip_option))] +// x: Option, +// #[builder(default = 10)] +// y: i32, +// #[builder(default = vec![y, 30, 40])] +// z: Vec, +// } +// +// assert!( +// Foo::builder().build() +// == Foo { +// x: None, +// y: 10, +// z: vec![10, 30, 40] +// } +// ); +// assert!( +// Foo::builder().x(1).build() +// == Foo { +// x: Some(1), +// y: 10, +// z: vec![10, 30, 40] +// } +// ); +// assert!( +// Foo::builder().y(2).build() +// == Foo { +// x: None, +// y: 2, +// z: vec![2, 30, 40] +// } +// ); +// assert!( +// Foo::builder().x(1).y(2).build() +// == Foo { +// x: Some(1), +// y: 2, +// z: vec![2, 30, 40] +// } +// ); +// assert!( +// Foo::builder().z(vec![1, 2, 3]).build() +// == Foo { +// x: None, +// y: 10, +// z: vec![1, 2, 3] +// } +// ); +// } +// +// // compile-fail tests for skip are in src/lib.rs out of necessity. These are just the bland +// // successful cases. +// #[test] +// fn test_skip() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(default, setter(skip))] +// x: i32, +// #[builder(setter(into))] +// y: i32, +// #[builder(default = y + 1, setter(skip))] +// z: i32, +// } +// +// assert!(Foo::builder().y(1_u8).build() == Foo { x: 0, y: 1, z: 2 }); +// } +// +// #[test] +// fn test_docs() { +// #[derive(TypedBuilder)] +// #[builder( +// builder_method_doc = "Point::builder() method docs", +// builder_type_doc = "PointBuilder type docs", +// build_method_doc = "PointBuilder.build() method docs" +// )] +// struct Point { +// #[allow(dead_code)] +// x: i32, +// #[builder( +// default = x, +// setter( +// doc = "Set `z`. If you don't specify a value it'll default to the value specified for `x`.", +// ), +// )] +// #[allow(dead_code)] +// y: i32, +// } +// +// let _ = Point::builder(); +// } +// +// #[test] +// fn test_builder_name() { +// #[derive(TypedBuilder)] +// struct Foo {} +// +// let _: FooBuilder<_> = Foo::builder(); +// } +// +// // NOTE: `test_builder_type_stability` and `test_builder_type_stability_with_other_generics` are +// // meant to ensure we don't break things for people that use custom `impl`s on the builder +// // type before the tuple field generic param transformation traits are in. +// // See: +// // - https://github.com/idanarye/rust-typed-builder/issues/22 +// // - https://github.com/idanarye/rust-typed-builder/issues/23 +// #[test] +// fn test_builder_type_stability() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// x: i32, +// y: i32, +// z: i32, +// } +// +// impl FooBuilder<((), Y, ())> { +// fn xz(self, x: i32, z: i32) -> FooBuilder<((i32,), Y, (i32,))> { +// self.x(x).z(z) +// } +// } +// +// assert!(Foo::builder().xz(1, 2).y(3).build() == Foo { x: 1, y: 3, z: 2 }); +// assert!(Foo::builder().xz(1, 2).y(3).build() == Foo::builder().x(1).z(2).y(3).build()); +// +// assert!(Foo::builder().y(1).xz(2, 3).build() == Foo { x: 2, y: 1, z: 3 }); +// assert!(Foo::builder().y(1).xz(2, 3).build() == Foo::builder().y(1).x(2).z(3).build()); +// } +// +// #[test] +// fn test_builder_type_stability_with_other_generics() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// x: X, +// y: Y, +// } +// +// impl FooBuilder<((), Y_), X, Y> { +// fn x_default(self) -> FooBuilder<((X,), Y_), X, Y> { +// self.x(X::default()) +// } +// } +// +// assert!(Foo::builder().x_default().y(1.0).build() == Foo { x: 0, y: 1.0 }); +// assert!( +// Foo::builder().y("hello".to_owned()).x_default().build() +// == Foo { +// x: "", +// y: "hello".to_owned() +// } +// ); +// } #[test] #[allow(clippy::items_after_statements)] fn test_builder_type_with_default_on_generic_type() { - #[derive(PartialEq, TypedBuilder)] - struct Types { - x: X, - y: Y, - } - assert!(Types::builder().x(()).y(()).build() == Types { x: (), y: () }); - - #[derive(PartialEq, TypedBuilder)] - struct TypeAndLifetime<'a, X, Y: Default, Z = usize> { - x: X, - y: Y, - z: &'a Z, - } + // #[derive(PartialEq, TypedBuilder)] + // struct Types { + // x: X, + // y: Y, + // } + // assert!(Types::builder().x(()).y(()).build() == Types { x: (), y: () }); + // + // #[derive(PartialEq, TypedBuilder)] + // struct TypeAndLifetime<'a, X, Y: Default, Z = usize> { + // x: X, + // y: Y, + // z: &'a Z, + // } let a = 0; - assert!(TypeAndLifetime::builder().x(()).y(0).z(&a).build() == TypeAndLifetime { x: (), y: 0, z: &0 }); + // assert!(TypeAndLifetime::builder().x(()).y(0).z(&a).build() == TypeAndLifetime { x: (), y: 0, z: &0 }); #[derive(PartialEq, TypedBuilder)] struct Foo<'a, X, Y: Default, Z: Default = usize, M = ()> { @@ -347,13 +347,13 @@ fn test_builder_type_with_default_on_generic_type() { m: M, } - impl<'a, X, Y: Default, M, X_, Y_, M_> FooBuilder<'a, (X_, Y_, (), M_), X, Y, usize, M> { + impl<'a, X, Y: Default, M, X_, Y_, M_> FooBuilder<'a, (X_, Y_, (usize,), M_), X, Y, usize, M> { fn z_default(self) -> FooBuilder<'a, (X_, Y_, (usize,), M_), X, Y, usize, M> { self.z(usize::default()) } } - impl<'a, X, Y: Default, Z: Default, X_, Y_, Z_> FooBuilder<'a, (X_, Y_, Z_, ()), X, Y, Z, ()> { + impl<'a, X, Y: Default, Z: Default, X_, Y_, Z_> FooBuilder<'a, (X_, Y_, Z_, ((),)), X, Y, Z, ()> { fn m_default(self) -> FooBuilder<'a, (X_, Y_, Z_, ((),)), X, Y, Z, ()> { self.m(()) } @@ -383,194 +383,211 @@ fn test_builder_type_with_default_on_generic_type() { ); } -#[test] -fn test_builder_type_skip_into() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - x: X, - } - - // compile test if rustc can infer type for `x` - Foo::builder().x(()).build(); - - assert!(Foo::builder().x(()).build() == Foo { x: () }); -} - -#[test] -fn test_default_code() { - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(default_code = "\"text1\".to_owned()")] - x: String, - - #[builder(default_code = r#""text2".to_owned()"#)] - y: String, - } - - assert!( - Foo::builder().build() - == Foo { - x: "text1".to_owned(), - y: "text2".to_owned() - } - ); -} - -#[test] -fn test_field_defaults_default_value() { - #[derive(PartialEq, TypedBuilder)] - #[builder(field_defaults(default = 12))] - struct Foo { - x: i32, - #[builder(!default)] - y: String, - #[builder(default = 13)] - z: i32, - } - - assert!( - Foo::builder().y("bla".to_owned()).build() - == Foo { - x: 12, - y: "bla".to_owned(), - z: 13 - } - ); -} - -#[test] -fn test_field_defaults_setter_options() { - #[derive(PartialEq, TypedBuilder)] - #[builder(field_defaults(setter(strip_option)))] - struct Foo { - x: Option, - #[builder(setter(!strip_option))] - y: i32, - } - - assert!(Foo::builder().x(1).y(2).build() == Foo { x: Some(1), y: 2 }); -} - -#[test] -fn test_clone_builder() { - #[derive(PartialEq, Default)] - struct Uncloneable; - - #[derive(PartialEq, TypedBuilder)] - struct Foo { - x: i32, - y: i32, - #[builder(default)] - z: Uncloneable, - } - - let semi_built = Foo::builder().x(1); - - assert!( - semi_built.clone().y(2).build() - == Foo { - x: 1, - y: 2, - z: Uncloneable - } - ); - assert!( - semi_built.y(3).build() - == Foo { - x: 1, - y: 3, - z: Uncloneable - } - ); -} - -#[test] -#[allow(clippy::items_after_statements)] -fn test_clone_builder_with_generics() { - #[derive(PartialEq, Default)] - struct Uncloneable; - - #[derive(PartialEq, TypedBuilder)] - struct Foo { - x: T, - y: i32, - } - - let semi_built1 = Foo::builder().x(1); - - assert!(semi_built1.clone().y(2).build() == Foo { x: 1, y: 2 }); - assert!(semi_built1.y(3).build() == Foo { x: 1, y: 3 }); - - let semi_built2 = Foo::builder().x("four"); - - assert!(semi_built2.clone().y(5).build() == Foo { x: "four", y: 5 }); - assert!(semi_built2.clone().y(6).build() == Foo { x: "four", y: 6 }); - - // Just to make sure it can build with generic bounds - #[allow(dead_code)] - #[derive(TypedBuilder)] - struct Bar - where - T: std::fmt::Display, - { - x: T, - } -} - -#[test] -fn test_builder_on_struct_with_keywords() { - #[allow(non_camel_case_types)] - #[derive(PartialEq, TypedBuilder)] - struct r#struct { - r#fn: u32, - #[builder(default, setter(strip_option))] - r#type: Option, - #[builder(default = Some(()), setter(skip))] - r#enum: Option<()>, - #[builder(setter(into))] - r#union: String, - } - - assert!( - r#struct::builder().r#fn(1).r#union("two").build() - == r#struct { - r#fn: 1, - r#type: None, - r#enum: Some(()), - r#union: "two".to_owned(), - } - ); -} - -#[test] -fn test_field_setter_transform() { - #[derive(PartialEq)] - struct Point { - x: i32, - y: i32, - } - - #[derive(PartialEq, TypedBuilder)] - struct Foo { - #[builder(setter(transform = |x: i32, y: i32| Point { x, y }))] - point: Point, - } - - assert!( - Foo::builder().point(1, 2).build() - == Foo { - point: Point { x: 1, y: 2 } - } - ); -} - -#[test] -fn test_build_method() { - #[derive(PartialEq, TypedBuilder)] - #[builder(build_method(vis="", name=__build))] - struct Foo { - x: i32, - } - - assert!(Foo::builder().x(1).__build() == Foo { x: 1 }); -} +// #[test] +// fn test_builder_type_skip_into() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// x: X, +// } +// +// // compile test if rustc can infer type for `x` +// Foo::builder().x(()).build(); +// +// assert!(Foo::builder().x(()).build() == Foo { x: () }); +// } +// +// #[test] +// fn test_default_code() { +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(default_code = "\"text1\".to_owned()")] +// x: String, +// +// #[builder(default_code = r#""text2".to_owned()"#)] +// y: String, +// } +// +// assert!( +// Foo::builder().build() +// == Foo { +// x: "text1".to_owned(), +// y: "text2".to_owned() +// } +// ); +// } +// +// #[test] +// fn test_field_defaults_default_value() { +// #[derive(PartialEq, TypedBuilder)] +// #[builder(field_defaults(default = 12))] +// struct Foo { +// x: i32, +// #[builder(!default)] +// y: String, +// #[builder(default = 13)] +// z: i32, +// } +// +// assert!( +// Foo::builder().y("bla".to_owned()).build() +// == Foo { +// x: 12, +// y: "bla".to_owned(), +// z: 13 +// } +// ); +// } +// +// #[test] +// fn test_field_defaults_setter_options() { +// #[derive(PartialEq, TypedBuilder)] +// #[builder(field_defaults(setter(strip_option)))] +// struct Foo { +// x: Option, +// #[builder(setter(!strip_option))] +// y: i32, +// } +// +// assert!(Foo::builder().x(1).y(2).build() == Foo { x: Some(1), y: 2 }); +// } +// +// #[test] +// fn test_clone_builder() { +// #[derive(PartialEq, Default)] +// struct Uncloneable; +// +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// x: i32, +// y: i32, +// #[builder(default)] +// z: Uncloneable, +// } +// +// let semi_built = Foo::builder().x(1); +// +// assert!( +// semi_built.clone().y(2).build() +// == Foo { +// x: 1, +// y: 2, +// z: Uncloneable +// } +// ); +// assert!( +// semi_built.y(3).build() +// == Foo { +// x: 1, +// y: 3, +// z: Uncloneable +// } +// ); +// } +// +// #[test] +// #[allow(clippy::items_after_statements)] +// fn test_clone_builder_with_generics() { +// #[derive(PartialEq, Default)] +// struct Uncloneable; +// +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// x: T, +// y: i32, +// } +// +// let semi_built1 = Foo::builder().x(1); +// +// assert!(semi_built1.clone().y(2).build() == Foo { x: 1, y: 2 }); +// assert!(semi_built1.y(3).build() == Foo { x: 1, y: 3 }); +// +// let semi_built2 = Foo::builder().x("four"); +// +// assert!(semi_built2.clone().y(5).build() == Foo { x: "four", y: 5 }); +// assert!(semi_built2.clone().y(6).build() == Foo { x: "four", y: 6 }); +// +// // Just to make sure it can build with generic bounds +// #[allow(dead_code)] +// #[derive(TypedBuilder)] +// struct Bar +// where +// T: std::fmt::Display, +// { +// x: T, +// } +// } +// +// #[test] +// fn test_builder_on_struct_with_keywords() { +// #[allow(non_camel_case_types)] +// #[derive(PartialEq, TypedBuilder)] +// struct r#struct { +// r#fn: u32, +// #[builder(default, setter(strip_option))] +// r#type: Option, +// #[builder(default = Some(()), setter(skip))] +// r#enum: Option<()>, +// #[builder(setter(into))] +// r#union: String, +// } +// +// assert!( +// r#struct::builder().r#fn(1).r#union("two").build() +// == r#struct { +// r#fn: 1, +// r#type: None, +// r#enum: Some(()), +// r#union: "two".to_owned(), +// } +// ); +// } +// +// #[test] +// fn test_field_setter_transform() { +// #[derive(PartialEq)] +// struct Point { +// x: i32, +// y: i32, +// } +// +// #[derive(PartialEq, TypedBuilder)] +// struct Foo { +// #[builder(setter(transform = |x: i32, y: i32| Point { x, y }))] +// point: Point, +// } +// +// assert!( +// Foo::builder().point(1, 2).build() +// == Foo { +// point: Point { x: 1, y: 2 } +// } +// ); +// } +// +// #[test] +// fn test_build_method() { +// #[derive(PartialEq, TypedBuilder)] +// #[builder(build_method(vis="", name=__build))] +// struct Foo { +// x: i32, +// } +// +// assert!(Foo::builder().x(1).__build() == Foo { x: 1 }); +// } +// +// #[test] +// fn test_struct_generic_defaults() { +// #[derive(TypedBuilder)] +// pub struct Props<'a, OnInput: FnOnce(usize) -> usize = Box usize>> { +// #[builder(default, setter(into))] +// pub class: Option<&'a str>, +// pub label: &'a str, +// #[builder(setter(into))] +// pub on_input: Option, +// } +// +// let props = Props::builder().label("label").on_input(|x: usize| x).build(); +// assert_eq!(props.class, None); +// assert_eq!(props.label, "label"); +// assert_eq!((props.on_input.unwrap())(123), 123); +// } From 7d22d8b2e78c0dd7b0c0c6592a2b1b32847bb9c2 Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 03:38:04 -0800 Subject: [PATCH 02/11] add support for specifying default values on fields which have the field's type if all generic parameters in the type have default values provided or there are no generic parameters at all --- Cargo.toml | 11 +- examples/complicate_build.rs | 4 +- proc-macros/Cargo.toml | 26 + {src => proc-macros/src}/field_info.rs | 64 +- proc-macros/src/lib.rs | 285 ++++++ {src => proc-macros/src}/struct_info.rs | 264 ++++-- {src => proc-macros/src}/util.rs | 9 +- src/lib.rs | 294 +----- tests/tests.rs | 1090 ++++++++++++----------- 9 files changed, 1118 insertions(+), 929 deletions(-) create mode 100644 proc-macros/Cargo.toml rename {src => proc-macros/src}/field_info.rs (87%) create mode 100644 proc-macros/src/lib.rs rename {src => proc-macros/src}/struct_info.rs (73%) rename {src => proc-macros/src}/util.rs (89%) diff --git a/Cargo.toml b/Cargo.toml index b750fe2b..ae1805be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,15 +11,8 @@ readme = "README.md" keywords = ["builder"] categories = ["rust-patterns"] -[lib] -proc-macro = true - [dependencies] -syn = { version = "1", features = ["full", "extra-traits"] } -quote = "1" -proc-macro2 = "1" -regex = "1.7.0" -prettyplease = { version = "0.1.21", optional = true } +typed-builder-proc-macros.path = "proc-macros" [features] -debug = ["dep:prettyplease"] +debug = ["typed-builder-proc-macros/debug"] diff --git a/examples/complicate_build.rs b/examples/complicate_build.rs index 50bbf109..8c6ecd23 100644 --- a/examples/complicate_build.rs +++ b/examples/complicate_build.rs @@ -1,5 +1,5 @@ mod scope { - use typed_builder::TypedBuilder; + use typed_builder::{BuilderOptional, TypedBuilder}; #[derive(PartialEq, TypedBuilder)] #[builder(build_method(vis="", name=__build))] @@ -25,7 +25,7 @@ mod scope { // We can use `cargo expand` to show code expanded by `TypedBuilder`, // copy the generated `__build` method, and modify the content of the build method. #[allow(non_camel_case_types)] - impl<__z: FooBuilder_Optional, __y: FooBuilder_Optional>> FooBuilder<((i32,), __y, __z)> { + impl<__z: BuilderOptional, __y: BuilderOptional>> FooBuilder<((i32,), __y, __z)> { pub fn build(self) -> Bar { let foo = self.__build(); Bar { diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml new file mode 100644 index 00000000..d83b9a6b --- /dev/null +++ b/proc-macros/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "typed-builder-proc-macros" +description = "Compile-time type-checked builder derive" +version = "0.11.0" +authors = ["IdanArye ", "Chris Morgan "] +edition = "2018" +license = "MIT/Apache-2.0" +repository = "https://github.com/idanarye/rust-typed-builder" +documentation = "https://idanarye.github.io/rust-typed-builder/" +readme = "README.md" +keywords = ["builder"] +categories = ["rust-patterns"] + +[lib] +proc-macro = true + +[dependencies] +either = "1.8.0" +prettyplease = { version = "*", optional = true } +proc-macro2 = "1" +quote = "1" +regex = "1.7.0" +syn = { version = "1", features = ["full", "extra-traits"] } + +[features] +debug = ["dep:prettyplease"] diff --git a/src/field_info.rs b/proc-macros/src/field_info.rs similarity index 87% rename from src/field_info.rs rename to proc-macros/src/field_info.rs index 46eb3f42..4c9b225d 100644 --- a/src/field_info.rs +++ b/proc-macros/src/field_info.rs @@ -1,9 +1,11 @@ +use either::Either::*; use proc_macro2::{Span, TokenStream}; use quote::quote; +use std::collections::HashSet; use syn::parse::Error; use syn::spanned::Spanned; -use crate::util::{empty_type, expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, TypeGenericDefaults}; +use crate::util::{empty_type, expr_to_single_string, GenericDefault, ident_to_type, path_to_single_string, strip_raw_ident_prefix}; #[derive(Debug)] pub struct FieldInfo<'a> { @@ -13,10 +15,11 @@ pub struct FieldInfo<'a> { pub ty: &'a syn::Type, pub builder_attr: FieldBuilderAttr, pub default_ty: Option<(syn::Type, syn::Expr)>, + pub used_default_generic_idents: HashSet, } impl<'a> FieldInfo<'a> { - pub fn new(ordinal: usize, field: &'a syn::Field, field_defaults: FieldBuilderAttr, type_generic_defaults: &TypeGenericDefaults) -> Result { + pub fn new(ordinal: usize, field: &'a syn::Field, field_defaults: FieldBuilderAttr, generic_defaults: &[GenericDefault]) -> Result { if let Some(ref name) = field.ident { let mut field_info = FieldInfo { ordinal, @@ -25,37 +28,44 @@ impl<'a> FieldInfo<'a> { ty: &field.ty, builder_attr: field_defaults.with(&field.attrs)?, default_ty: None, + used_default_generic_idents: HashSet::default(), } .post_process()?; - if field_info.builder_attr.default.is_none() { - let mut ty_includes_params_without_defaults = false; - let mut ty_includes_params_with_defaults = false; - let ty = &field.ty; - let mut ty_str = format!("{}", quote! { #ty }); - for (type_param, default_type) in type_generic_defaults.iter() { - if type_param.is_match(&ty_str) { - match default_type.as_ref() { - Some(default_type) => { - ty_includes_params_with_defaults = true; - ty_str = type_param.replace(&ty_str, default_type).into(); - }, - None => { - ty_includes_params_without_defaults = true; - break; - } + let mut ty_includes_params_without_defaults = false; + let mut ty_includes_params_with_defaults = false; + let ty = &field.ty; + let mut ty_str = format!("{}", quote! { #ty }); + for (generic_param, regular_expression, default_type) in generic_defaults.iter() { + if regular_expression.is_match(&ty_str) { + match default_type.as_ref() { + Some(default_type) => { + ty_includes_params_with_defaults = true; + ty_str = regular_expression.replace(&ty_str, default_type).into(); + match generic_param { + Left(type_param) => field_info.used_default_generic_idents.insert(type_param.ident.clone()), + Right(const_param) => field_info.used_default_generic_idents.insert(const_param.ident.clone()), + }; + }, + None => { + ty_includes_params_without_defaults = true; + break; } } } - if !ty_includes_params_without_defaults && ty_includes_params_with_defaults { - use std::str::FromStr; - let expr_str = format!("(<{ty_str} as Default>::default(),)"); - let ty_str = format!("({ty_str},)"); - field_info.default_ty = Some(( - syn::parse(TokenStream::from_str(&ty_str)?.into())?, - syn::parse(TokenStream::from_str(&expr_str)?.into())?, - )); - } + } + if !ty_includes_params_without_defaults && ty_includes_params_with_defaults { + use std::str::FromStr; + let expr_str = format!("(<{ty_str} as Default>::default(),)"); + let ty_str = format!("({ty_str},)"); + field_info.default_ty = Some(( + syn::parse(TokenStream::from_str(&ty_str)?.into())?, + if let Some(default_expr) = field_info.builder_attr.default.clone() { + syn::parse(quote! { (#default_expr,) }.into())? + } else { + syn::parse(TokenStream::from_str(&expr_str)?.into())? + }, + )); } Ok(field_info) diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs new file mode 100644 index 00000000..dc09781c --- /dev/null +++ b/proc-macros/src/lib.rs @@ -0,0 +1,285 @@ +extern crate proc_macro; // Needed even though it's the 2018 edition. See https://github.com/idanarye/rust-typed-builder/issues/57. + +use proc_macro2::TokenStream; + +use syn::parse::Error; +use syn::spanned::Spanned; +use syn::{parse_macro_input, DeriveInput}; + +use quote::quote; + +mod field_info; +mod struct_info; +mod util; + +/// `TypedBuilder` is not a real type - deriving it will generate a `::builder()` method on your +/// struct that will return a compile-time checked builder. Set the fields using setters with the +/// same name as the struct's fields and call `.build()` when you are done to create your object. +/// +/// Trying to set the same fields twice will generate a compile-time error. Trying to build without +/// setting one of the fields will also generate a compile-time error - unless that field is marked +/// as `#[builder(default)]`, in which case the `::default()` value of it's type will be picked. If +/// you want to set a different default, use `#[builder(default=...)]`. +/// +/// # Examples +/// +/// ``` +/// use typed_builder::TypedBuilder; +/// +/// #[derive(PartialEq, TypedBuilder)] +/// struct Foo { +/// // Mandatory Field: +/// x: i32, +/// +/// // #[builder(default)] without parameter - use the type's default +/// // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` +/// #[builder(default, setter(strip_option))] +/// y: Option, +/// +/// // Or you can set the default +/// #[builder(default=20)] +/// z: i32, +/// } +/// +/// assert!( +/// Foo::builder().x(1).y(2).z(3).build() +/// == Foo { x: 1, y: Some(2), z: 3, }); +/// +/// // Change the order of construction: +/// assert!( +/// Foo::builder().z(1).x(2).y(3).build() +/// == Foo { x: 2, y: Some(3), z: 1, }); +/// +/// // Optional fields are optional: +/// assert!( +/// Foo::builder().x(1).build() +/// == Foo { x: 1, y: None, z: 20, }); +/// +/// // This will not compile - because we did not set x: +/// // Foo::builder().build(); +/// +/// // This will not compile - because we set y twice: +/// // Foo::builder().x(1).y(2).y(3); +/// ``` +/// +/// # Customisation with attributes +/// +/// In addition to putting `#[derive(TypedBuilder)]` on a type, you can specify a `#[builder(…)]` +/// attribute on the type, and on any fields in it. +/// +/// On the **type**, the following values are permitted: +/// +/// - `doc`: enable documentation of the builder type. By default, the builder type is given +/// `#[doc(hidden)]`, so that the `builder()` method will show `FooBuilder` as its return type, +/// but it won't be a link. If you turn this on, the builder type and its `build` method will get +/// sane defaults. The field methods on the builder will be undocumented by default. +/// +/// - `build_method(...)`: customize the final build method +/// - `vis = "…"`: sets the visibility of the build method, default is `pub` +/// - `name = …`: sets the fn name of the build method, default is `build` +/// +/// - `builder_method_doc = "…"` replaces the default documentation that will be generated for the +/// `builder()` method of the type for which the builder is being generated. +/// +/// - `builder_type_doc = "…"` replaces the default documentation that will be generated for the +/// builder type. Setting this implies `doc`. +/// +/// - `build_method_doc = "…"` replaces the default documentation that will be generated for the +/// `build()` method of the builder type. Setting this implies `doc`. +/// +/// - `field_defaults(...)` is structured like the `#[builder(...)]` attribute you can put on the +/// fields and sets default options for fields of the type. If specific field need to revert some +/// options to the default defaults they can prepend `!` to the option they need to revert, and +/// it would ignore the field defaults for that option in that field. +/// +/// ``` +/// use typed_builder::TypedBuilder; +/// +/// #[derive(TypedBuilder)] +/// #[builder(field_defaults(default, setter(strip_option)))] +/// struct Foo { +/// // Defaults to None, options-stripping is performed: +/// x: Option, +/// +/// // Defaults to 0, option-stripping is not performed: +/// #[builder(setter(!strip_option))] +/// y: i32, +/// +/// // Defaults to Some(13), option-stripping is performed: +/// #[builder(default = Some(13))] +/// z: Option, +/// +/// // Accepts params `(x: f32, y: f32)` +/// #[builder(setter(!strip_option, transform = |x: f32, y: f32| Point { x, y }))] +/// w: Point, +/// } +/// +/// #[derive(Default)] +/// struct Point { x: f32, y: f32 } +/// ``` +/// +/// On each **field**, the following values are permitted: +/// +/// - `default`: make the field optional, defaulting to `Default::default()`. This requires that +/// the field type implement `Default`. Mutually exclusive with any other form of default. +/// +/// - `default = …`: make the field optional, defaulting to the expression `…`. +/// +/// - `default_code = "…"`: make the field optional, defaulting to the expression `…`. Note that +/// you need to enclose it in quotes, which allows you to use it together with other custom +/// derive proc-macro crates that complain about "expected literal". +/// Note that if `...` contains a string, you can use raw string literals to avoid escaping the +/// double quotes - e.g. `#[builder(default_code = r#""default text".to_owned()"#)]`. +/// +/// - `setter(...)`: settings for the field setters. The following values are permitted inside: +/// +/// - `doc = "…"`: sets the documentation for the field's setter on the builder type. This will be +/// of no value unless you enable docs for the builder type with `#[builder(doc)]` or similar on +/// the type. +/// +/// - `skip`: do not define a method on the builder for this field. This requires that a default +/// be set. +/// +/// - `into`: automatically convert the argument of the setter method to the type of the field. +/// Note that this conversion interferes with Rust's type inference and integer literal +/// detection, so this may reduce ergonomics if the field type is generic or an unsigned integer. +/// +/// - `strip_option`: for `Option<...>` fields only, this makes the setter wrap its argument with +/// `Some(...)`, relieving the caller from having to do this. Note that with this setting on +/// one cannot set the field to `None` with the setter - so the only way to get it to be `None` +/// is by using `#[builder(default)]` and not calling the field's setter. +/// +/// - `strip_bool`: for `bool` fields only, this makes the setter receive no arguments and simply +/// set the field's value to `true`. When used, the `default` is automatically set to `false`. +/// +/// - `transform = |param1: Type1, param2: Type2 ...| expr`: this makes the setter accept +/// `param1: Type1, param2: Type2 ...` instead of the field type itself. The parameters are +/// transformed into the field type using the expression `expr`. The transformation is performed +/// when the setter is called. +#[proc_macro_derive(TypedBuilder, attributes(builder))] +pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let tokens: proc_macro::TokenStream = match impl_my_derive(&input) { + Ok(output) => output.into(), + Err(error) => error.to_compile_error().into(), + }; + #[cfg(feature = "debug")] + println!("{}", prettyplease::unparse(&syn::parse_file(&format!("{tokens}")).unwrap())); + tokens +} + +fn impl_my_derive(ast: &syn::DeriveInput) -> Result { + let data = match &ast.data { + syn::Data::Struct(data) => match &data.fields { + syn::Fields::Named(fields) => { + let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?; + let builder_creation = struct_info.builder_creation_impl()?; + let fields = struct_info + .included_fields() + .map(|f| struct_info.field_impl(f)) + .collect::, _>>()?; + let fields = quote!(#(#fields)*).into_iter(); + let required_fields = struct_info + .included_fields() + .filter(|f| f.builder_attr.default.is_none() && f.default_ty.is_none()) + .map(|f| struct_info.required_field_impl(f)) + .collect::>(); + let build_method = struct_info.build_method_impl(); + + quote! { + #builder_creation + #( #fields )* + #( #required_fields )* + #build_method + } + } + syn::Fields::Unnamed(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for tuple structs")), + syn::Fields::Unit => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unit structs")), + }, + syn::Data::Enum(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for enums")), + syn::Data::Union(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unions")), + }; + Ok(data) +} + +// It'd be nice for the compilation tests to live in tests/ with the rest, but short of pulling in +// some other test runner for that purpose (e.g. compiletest_rs), rustdoc compile_fail in this +// crate is all we can use. + +#[doc(hidden)] +/// When a property is skipped, you can't set it: +/// (“method `y` not found for this”) +/// +/// ```compile_fail +/// use typed_builder::TypedBuilder; +/// +/// #[derive(PartialEq, TypedBuilder)] +/// struct Foo { +/// #[builder(default, setter(skip))] +/// y: i8, +/// } +/// +/// let _ = Foo::builder().y(1i8).build(); +/// ``` +/// +/// But you can build a record: +/// +/// ``` +/// use typed_builder::TypedBuilder; +/// +/// #[derive(PartialEq, TypedBuilder)] +/// struct Foo { +/// #[builder(default, setter(skip))] +/// y: i8, +/// } +/// +/// let _ = Foo::builder().build(); +/// ``` +/// +/// `skip` without `default` is disallowed: +/// (“error: #[builder(skip)] must be accompanied by default”) +/// +/// ```compile_fail +/// use typed_builder::TypedBuilder; +/// +/// #[derive(PartialEq, TypedBuilder)] +/// struct Foo { +/// #[builder(setter(skip))] +/// y: i8, +/// } +/// ``` +/// +/// `clone` does not work if non-Clone fields have already been set +/// +/// ```compile_fail +/// use typed_builder::TypedBuilder; +/// +/// #[derive(Default)] +/// struct Uncloneable; +/// +/// #[derive(TypedBuilder)] +/// struct Foo { +/// x: Uncloneable, +/// y: i32, +/// } +/// +/// let _ = Foo::builder().x(Uncloneable).clone(); +/// ``` +/// +/// Same, but with generics +/// +/// ```compile_fail +/// use typed_builder::TypedBuilder; +/// +/// #[derive(Default)] +/// struct Uncloneable; +/// +/// #[derive(TypedBuilder)] +/// struct Foo { +/// x: T, +/// y: i32, +/// } +/// +/// let _ = Foo::builder().x(Uncloneable).clone(); +/// ``` +fn _compile_fail_tests() {} diff --git a/src/struct_info.rs b/proc-macros/src/struct_info.rs similarity index 73% rename from src/struct_info.rs rename to proc-macros/src/struct_info.rs index d6bf36d2..711ac651 100644 --- a/src/struct_info.rs +++ b/proc-macros/src/struct_info.rs @@ -1,12 +1,15 @@ +use either::Either::*; use proc_macro2::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use regex::Regex; +use std::iter::FromIterator; use syn::parse::Error; +use syn::punctuated::Punctuated; use crate::field_info::{FieldBuilderAttr, FieldInfo}; use crate::util::{ - empty_type_tuple, expr_to_single_string, expr_tuple, make_punctuated_single, modify_types_generics_hack, - path_to_single_string, strip_raw_ident_prefix, TypeGenericDefaults, type_tuple, + empty_type_tuple, expr_to_single_string, expr_tuple, GenericDefault, make_punctuated_single, modify_types_generics_hack, + path_to_single_string, strip_raw_ident_prefix, type_tuple, }; #[derive(Debug)] @@ -15,18 +18,17 @@ pub struct StructInfo<'a> { pub name: &'a syn::Ident, pub generics: &'a syn::Generics, pub fields: Vec>, - // all generics, just with default types removed pub generics_without_defaults: syn::Generics, - - // only generics which had no defaults specified + /// equivalent of a TyGenerics struct where TypeParams are replaced by their defaults if provided + pub ty_generics_with_defaults: Punctuated, + /// only generics which had no defaults specified pub no_default_generics: syn::Generics, - - pub type_generic_defaults: TypeGenericDefaults, + /// map of word matching regex patterns for TypeParam idents to their corresponding default type + pub generic_defaults: Vec, pub builder_attr: TypeBuilderAttr, pub builder_name: syn::Ident, - pub conversion_helper_trait_name: syn::Ident, pub core: syn::Ident, } @@ -51,32 +53,78 @@ impl<'a> StructInfo<'a> { eq_token: None, default: None, }), + syn::GenericParam::Const(const_param) => syn::GenericParam::Const(syn::ConstParam { + attrs: const_param.attrs, + const_token: const_param.const_token, + ident: const_param.ident, + colon_token: const_param.colon_token, + ty: const_param.ty, + eq_token: None, + default: None, + }), param => param, }) .collect(); + let ty_generics_with_defaults: Punctuated<_, syn::token::Comma> = ast.generics.params + .clone() + .into_iter() + .map::, _>(|param| match param { + syn::GenericParam::Type(type_param) => match type_param.default { + Some(default) => syn::parse(proc_macro::TokenStream::from(quote!(#default))), + None => { + let ident = type_param.ident; + syn::parse(proc_macro::TokenStream::from(quote!(#ident))) + }, + }, + syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) => syn::parse(proc_macro::TokenStream::from(quote!(#lifetime))), + syn::GenericParam::Const(const_param) => match const_param.default { + Some(default) => syn::parse(proc_macro::TokenStream::from(quote!(#default))), + None => { + let ident = const_param.ident; + syn::parse(proc_macro::TokenStream::from(quote!(#ident))) + }, + }, + }) + .collect::>()?; + let mut no_default_generics = ast.generics.clone(); - let mut type_generic_defaults = TypeGenericDefaults::default(); + let mut generic_defaults = Vec::::default(); no_default_generics.params = no_default_generics.params .into_iter() .filter_map(|param| match param { syn::GenericParam::Type(type_param) => match type_param.default.clone() { Some(default) => { let ident = &type_param.ident; - type_generic_defaults.push(( - Regex::new(format!(r#"\b{}\b"#, quote!(#ident)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#type_param)))), - Some(format!("{}", quote!(#default)).trim().to_string()), - )); + let regular_expression = Regex::new(format!(r#"\b{}\b"#, quote!(#ident)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#type_param)))); + generic_defaults.push((Left(type_param), regular_expression, Some(format!("{}", quote!(#default)).trim().to_string()))); None }, None => { - type_generic_defaults.push(( + generic_defaults.push(( + Left(type_param.clone()), Regex::new(format!(r#"\b{}\b"#, quote!(#type_param)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#type_param)))), None, )); Some(syn::GenericParam::Type(type_param)) }, }, + syn::GenericParam::Const(const_param) => match const_param.default.clone() { + Some(default) => { + let ident = &const_param.ident; + let regular_expression = Regex::new(format!(r#"\b{}\b"#, quote!(#ident)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#const_param)))); + generic_defaults.push((Right(const_param), regular_expression, Some(format!("{}", quote!(#default)).trim().to_string()))); + None + }, + None => { + generic_defaults.push(( + Right(const_param.clone()), + Regex::new(format!(r#"\b{}\b"#, quote!(#const_param)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#const_param)))), + None, + )); + Some(syn::GenericParam::Const(const_param)) + }, + }, param => Some(param), }) .collect(); @@ -87,15 +135,15 @@ impl<'a> StructInfo<'a> { generics: &ast.generics, fields: fields .enumerate() - .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone(), &type_generic_defaults)) + .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone(), &generic_defaults)) .collect::>()?, builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()), - conversion_helper_trait_name: syn::Ident::new(&format!("{}_Optional", builder_name), proc_macro2::Span::call_site()), core: syn::Ident::new(&format!("{}_core", builder_name), proc_macro2::Span::call_site()), builder_attr, generics_without_defaults, + ty_generics_with_defaults, no_default_generics, - type_generic_defaults, + generic_defaults, }) } @@ -105,12 +153,59 @@ impl<'a> StructInfo<'a> { generics } + fn modify_generics_alter_if_used_default_generic(&self, mut mutator: F, field: &FieldInfo) -> syn::Generics { + let mut generics = self.generics.clone(); + generics.params + .iter_mut() + .for_each(|param| match param { + syn::GenericParam::Type(ref mut type_param) => if type_param.default.is_some() && field.used_default_generic_idents.contains(&type_param.ident) { + type_param.ident = format_ident_target_generic_default(&type_param.ident); + }, + syn::GenericParam::Const(ref mut const_param) => if const_param.default.is_some() && field.used_default_generic_idents.contains(&const_param.ident) { + const_param.ident = format_ident_target_generic_default(&const_param.ident); + }, + _ => {}, + }); + mutator(&mut generics); + generics + } + fn modify_generics_with_no_defaults(&self, mut mutator: F) -> syn::Generics { let mut generics = self.generics_without_defaults.clone(); mutator(&mut generics); generics } + fn ty_generics_with_defaults_except_field(&self, field: &FieldInfo) -> Result, Error> { + self.generics.params + .clone() + .into_iter() + .map::, _>(|param| match param { + syn::GenericParam::Type(type_param) => match field.used_default_generic_idents.contains(&type_param.ident) { + true => { + let ident = format_ident_target_generic_default(&type_param.ident); + syn::parse(proc_macro::TokenStream::from(quote!(#ident))) + }, + false => { + let ident = &type_param.ident; + syn::parse(proc_macro::TokenStream::from(quote!(#ident))) + }, + }, + syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) => syn::parse(proc_macro::TokenStream::from(quote!(#lifetime))), + syn::GenericParam::Const(const_param) => match field.used_default_generic_idents.contains(&const_param.ident) { + true => { + let ident = format_ident_target_generic_default(&const_param.ident); + syn::parse(proc_macro::TokenStream::from(quote!(#ident))) + }, + false => { + let ident = &const_param.ident; + syn::parse(proc_macro::TokenStream::from(quote!(#ident))) + }, + }, + }) + .collect::>() + } + pub fn builder_creation_impl(&self) -> Result { let StructInfo { ref vis, @@ -118,7 +213,7 @@ impl<'a> StructInfo<'a> { ref builder_name, .. } = *self; - let (impl_generics, ty_generics, where_clause) = self.generics_without_defaults.split_for_impl(); + let (impl_generics, ty_generics, where_clause) = self.no_default_generics.split_for_impl(); let all_fields_param = syn::GenericParam::Type(syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into()); let b_generics = self.modify_generics_with_no_defaults(|g| { @@ -126,9 +221,16 @@ impl<'a> StructInfo<'a> { }); let expr_empties_tuple = expr_tuple(self.included_fields().map(|f| f.default_expr())); let ty_empties_tuple = type_tuple(self.included_fields().map(|f| f.default_type())); - let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { - args.insert(0, syn::GenericArgument::Type(ty_empties_tuple.clone().into())); - }); + + let mut ty_generics_with_defaults = self.ty_generics_with_defaults.clone(); + ty_generics_with_defaults.insert(0, syn::GenericArgument::Type(ty_empties_tuple.clone().into())); + + let generics_with_empty = syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: ty_generics_with_defaults, + gt_token: Default::default(), + }; let phantom_generics = self.generics.params.iter().map(|param| match param { syn::GenericParam::Lifetime(lifetime) => { let lifetime = &lifetime.lifetime; @@ -226,31 +328,6 @@ impl<'a> StructInfo<'a> { }) } - // TODO: once the proc-macro crate limitation is lifted, make this an util trait of this - // crate. - pub fn conversion_helper_impl(&self) -> TokenStream { - let trait_name = &self.conversion_helper_trait_name; - quote! { - #[doc(hidden)] - #[allow(dead_code, non_camel_case_types, non_snake_case)] - pub trait #trait_name { - fn into_value T>(self, default: F) -> T; - } - - impl #trait_name for () { - fn into_value T>(self, default: F) -> T { - default() - } - } - - impl #trait_name for (T,) { - fn into_value T>(self, _: F) -> T { - self.0 - } - } - } - } - pub fn field_impl(&self, field: &FieldInfo) -> Result { let StructInfo { ref builder_name, .. } = *self; @@ -269,7 +346,8 @@ impl<'a> StructInfo<'a> { ty: field_type, .. } = field; - let mut ty_generics: Vec = self + + let mut target_generics: Vec = self .generics .params .iter() @@ -285,9 +363,17 @@ impl<'a> StructInfo<'a> { } }) .collect(); + + let mut ty_generics: Vec = if !field.used_default_generic_idents.is_empty() { + self.ty_generics_with_defaults_except_field(field)? + } else { + target_generics.clone() + }; + let mut target_generics_tuple = empty_type_tuple(); let mut ty_generics_tuple = empty_type_tuple(); - let generics = self.modify_generics(|g| { + + let modify_generics_callback = |g: &mut syn::Generics| { let index_after_lifetime_in_generics = g .params .iter() @@ -306,8 +392,13 @@ impl<'a> StructInfo<'a> { ty_generics_tuple.elems.push_punct(Default::default()); target_generics_tuple.elems.push_punct(Default::default()); } - }); - let mut target_generics = ty_generics.clone(); + }; + let generics = if !field.used_default_generic_idents.is_empty() { + self.modify_generics_alter_if_used_default_generic(modify_generics_callback, field) + } else { + self.modify_generics(modify_generics_callback) + }; + let index_after_lifetime_in_generics = target_generics .iter() .filter(|arg| matches!(arg, syn::GenericArgument::Lifetime(_))) @@ -342,6 +433,34 @@ impl<'a> StructInfo<'a> { (quote!(#arg_type), quote!(#field_name)) }; + let fn_generics = { + let ty_str = format!("{arg_type}"); + let mut generic_params = vec![]; + for (generic_param, regular_expression, default_type) in self.generic_defaults.iter() { + if default_type.is_some() && regular_expression.is_match(&ty_str) { + match generic_param { + Left(type_param) => { + let mut type_param = type_param.clone(); + type_param.eq_token = None; + type_param.default = None; + generic_params.push(quote!(#type_param)); + }, + Right(const_param) => { + let mut const_param = const_param.clone(); + const_param.eq_token = None; + const_param.default = None; + generic_params.push(quote!(#const_param)); + }, + } + } + } + if generic_params.is_empty() { + quote!() + } else { + quote!(<#(#generic_params),*>) + } + }; + 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 { @@ -355,7 +474,7 @@ impl<'a> StructInfo<'a> { }; // repeated field impl cannot exist if field includes use of a default type because of overlapping impls - let repeated_field_impl = if field.default_ty.is_none() { + let repeated_field_impl = if field.used_default_generic_idents.is_empty() { let repeated_fields_error_type_name = syn::Ident::new( &format!( "{}_Error_Repeated_field_{}", @@ -389,12 +508,12 @@ impl<'a> StructInfo<'a> { #[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 ),* > { + pub fn #field_name #fn_generics (self, #param_list) -> #builder_name < #( #target_generics ),* > { let #field_name = (#arg_expr,); let ( #(#descructuring,)* ) = self.fields; #builder_name { fields: ( #(#reconstructing,)* ), - phantom: self.phantom, + phantom: ::core::default::Default::default(), } } } @@ -522,16 +641,24 @@ impl<'a> StructInfo<'a> { paren_token: None, lifetimes: None, modifier: syn::TraitBoundModifier::None, - path: syn::PathSegment { - ident: self.conversion_helper_trait_name.clone(), - arguments: syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { - colon2_token: None, - lt_token: Default::default(), - args: make_punctuated_single(syn::GenericArgument::Type(field.ty.clone())), - gt_token: Default::default(), - }), - } - .into(), + path: syn::Path { + leading_colon: None, + segments: Punctuated::from_iter(vec![ + syn::PathSegment { + ident: format_ident!("typed_builder"), + arguments: syn::PathArguments::None, + }, + syn::PathSegment { + ident: format_ident!("BuilderOptional"), + arguments: syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: make_punctuated_single(syn::GenericArgument::Type(field.ty.clone())), + gt_token: Default::default(), + }), + }, + ].into_iter()), + }, }; let mut generic_param: syn::TypeParam = field.generic_ident.clone().into(); generic_param.bounds.push(trait_ref.into()); @@ -561,7 +688,6 @@ impl<'a> StructInfo<'a> { let descructuring = self.included_fields().map(|f| f.name); - let helper_trait_name = &self.conversion_helper_trait_name; // The default of a field can refer to earlier-defined fields, which we handle by // writing out a bunch of `let` statements first, which can each refer to earlier ones. // This means that field ordering may actually be significant, which isn't ideal. We could @@ -572,8 +698,10 @@ impl<'a> StructInfo<'a> { if let Some(ref default) = field.builder_attr.default { if field.builder_attr.setter.skip.is_some() { quote!(let #name = #default;) + } else if !field.used_default_generic_idents.is_empty() { + quote!(let #name = typed_builder::BuilderOptional::into_value(#name, || None);) } else { - quote!(let #name = #helper_trait_name::into_value(#name, || #default);) + quote!(let #name = typed_builder::BuilderOptional::into_value(#name, || Some(#default));) } } else { quote!(let #name = #name.0;) @@ -779,3 +907,7 @@ impl TypeBuilderAttr { } } } + +fn format_ident_target_generic_default(ident: &syn::Ident) -> syn::Ident { + format_ident!("{ident}__") +} diff --git a/src/util.rs b/proc-macros/src/util.rs similarity index 89% rename from src/util.rs rename to proc-macros/src/util.rs index b2cb3ab1..4c7bf5df 100644 --- a/src/util.rs +++ b/proc-macros/src/util.rs @@ -1,10 +1,13 @@ +use either::Either; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use regex::Regex; -// keys are regex patterns of format /\btype_param\b/ -// values are options of the default type provided, formatted and trimmed as a String -pub type TypeGenericDefaults = Vec<(Regex, Option)>; +// tuple of: +// - TypeParam or ConstParam +// - regex pattern of format /\b$ident\b/ where $ident is the ident of the first tuple item +// - default type if provided, formatted and trimmed as a String +pub type GenericDefault = (Either, Regex, Option); pub fn path_to_single_string(path: &syn::Path) -> Option { if path.leading_colon.is_some() { diff --git a/src/lib.rs b/src/lib.rs index ffd08d66..02b57c77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,287 +1,17 @@ -extern crate proc_macro; // Needed even though it's the 2018 edition. See https://github.com/idanarye/rust-typed-builder/issues/57. +pub use typed_builder_proc_macros::*; -use proc_macro2::TokenStream; - -use syn::parse::Error; -use syn::spanned::Spanned; -use syn::{parse_macro_input, DeriveInput}; - -use quote::quote; - -mod field_info; -mod struct_info; -mod util; - -/// `TypedBuilder` is not a real type - deriving it will generate a `::builder()` method on your -/// struct that will return a compile-time checked builder. Set the fields using setters with the -/// same name as the struct's fields and call `.build()` when you are done to create your object. -/// -/// Trying to set the same fields twice will generate a compile-time error. Trying to build without -/// setting one of the fields will also generate a compile-time error - unless that field is marked -/// as `#[builder(default)]`, in which case the `::default()` value of it's type will be picked. If -/// you want to set a different default, use `#[builder(default=...)]`. -/// -/// # Examples -/// -/// ``` -/// use typed_builder::TypedBuilder; -/// -/// #[derive(PartialEq, TypedBuilder)] -/// struct Foo { -/// // Mandatory Field: -/// x: i32, -/// -/// // #[builder(default)] without parameter - use the type's default -/// // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` -/// #[builder(default, setter(strip_option))] -/// y: Option, -/// -/// // Or you can set the default -/// #[builder(default=20)] -/// z: i32, -/// } -/// -/// assert!( -/// Foo::builder().x(1).y(2).z(3).build() -/// == Foo { x: 1, y: Some(2), z: 3, }); -/// -/// // Change the order of construction: -/// assert!( -/// Foo::builder().z(1).x(2).y(3).build() -/// == Foo { x: 2, y: Some(3), z: 1, }); -/// -/// // Optional fields are optional: -/// assert!( -/// Foo::builder().x(1).build() -/// == Foo { x: 1, y: None, z: 20, }); -/// -/// // This will not compile - because we did not set x: -/// // Foo::builder().build(); -/// -/// // This will not compile - because we set y twice: -/// // Foo::builder().x(1).y(2).y(3); -/// ``` -/// -/// # Customisation with attributes -/// -/// In addition to putting `#[derive(TypedBuilder)]` on a type, you can specify a `#[builder(…)]` -/// attribute on the type, and on any fields in it. -/// -/// On the **type**, the following values are permitted: -/// -/// - `doc`: enable documentation of the builder type. By default, the builder type is given -/// `#[doc(hidden)]`, so that the `builder()` method will show `FooBuilder` as its return type, -/// but it won't be a link. If you turn this on, the builder type and its `build` method will get -/// sane defaults. The field methods on the builder will be undocumented by default. -/// -/// - `build_method(...)`: customize the final build method -/// - `vis = "…"`: sets the visibility of the build method, default is `pub` -/// - `name = …`: sets the fn name of the build method, default is `build` -/// -/// - `builder_method_doc = "…"` replaces the default documentation that will be generated for the -/// `builder()` method of the type for which the builder is being generated. -/// -/// - `builder_type_doc = "…"` replaces the default documentation that will be generated for the -/// builder type. Setting this implies `doc`. -/// -/// - `build_method_doc = "…"` replaces the default documentation that will be generated for the -/// `build()` method of the builder type. Setting this implies `doc`. -/// -/// - `field_defaults(...)` is structured like the `#[builder(...)]` attribute you can put on the -/// fields and sets default options for fields of the type. If specific field need to revert some -/// options to the default defaults they can prepend `!` to the option they need to revert, and -/// it would ignore the field defaults for that option in that field. -/// -/// ``` -/// use typed_builder::TypedBuilder; -/// -/// #[derive(TypedBuilder)] -/// #[builder(field_defaults(default, setter(strip_option)))] -/// struct Foo { -/// // Defaults to None, options-stripping is performed: -/// x: Option, -/// -/// // Defaults to 0, option-stripping is not performed: -/// #[builder(setter(!strip_option))] -/// y: i32, -/// -/// // Defaults to Some(13), option-stripping is performed: -/// #[builder(default = Some(13))] -/// z: Option, -/// -/// // Accepts params `(x: f32, y: f32)` -/// #[builder(setter(!strip_option, transform = |x: f32, y: f32| Point { x, y }))] -/// w: Point, -/// } -/// -/// #[derive(Default)] -/// struct Point { x: f32, y: f32 } -/// ``` -/// -/// On each **field**, the following values are permitted: -/// -/// - `default`: make the field optional, defaulting to `Default::default()`. This requires that -/// the field type implement `Default`. Mutually exclusive with any other form of default. -/// -/// - `default = …`: make the field optional, defaulting to the expression `…`. -/// -/// - `default_code = "…"`: make the field optional, defaulting to the expression `…`. Note that -/// you need to enclose it in quotes, which allows you to use it together with other custom -/// derive proc-macro crates that complain about "expected literal". -/// Note that if `...` contains a string, you can use raw string literals to avoid escaping the -/// double quotes - e.g. `#[builder(default_code = r#""default text".to_owned()"#)]`. -/// -/// - `setter(...)`: settings for the field setters. The following values are permitted inside: -/// -/// - `doc = "…"`: sets the documentation for the field's setter on the builder type. This will be -/// of no value unless you enable docs for the builder type with `#[builder(doc)]` or similar on -/// the type. -/// -/// - `skip`: do not define a method on the builder for this field. This requires that a default -/// be set. -/// -/// - `into`: automatically convert the argument of the setter method to the type of the field. -/// Note that this conversion interferes with Rust's type inference and integer literal -/// detection, so this may reduce ergonomics if the field type is generic or an unsigned integer. -/// -/// - `strip_option`: for `Option<...>` fields only, this makes the setter wrap its argument with -/// `Some(...)`, relieving the caller from having to do this. Note that with this setting on -/// one cannot set the field to `None` with the setter - so the only way to get it to be `None` -/// is by using `#[builder(default)]` and not calling the field's setter. -/// -/// - `strip_bool`: for `bool` fields only, this makes the setter receive no arguments and simply -/// set the field's value to `true`. When used, the `default` is automatically set to `false`. -/// -/// - `transform = |param1: Type1, param2: Type2 ...| expr`: this makes the setter accept -/// `param1: Type1, param2: Type2 ...` instead of the field type itself. The parameters are -/// transformed into the field type using the expression `expr`. The transformation is performed -/// when the setter is called. -#[proc_macro_derive(TypedBuilder, attributes(builder))] -pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let tokens: proc_macro::TokenStream = match impl_my_derive(&input) { - Ok(output) => output.into(), - Err(error) => error.to_compile_error().into(), - }; - #[cfg(feature = "debug")] - println!("{}", prettyplease::unparse(&syn::parse_file(&format!("{}", proc_macro2::TokenStream::from(tokens.clone()))).unwrap())); - tokens +pub trait BuilderOptional { + fn into_value Option>(self, default: F) -> T; } -fn impl_my_derive(ast: &syn::DeriveInput) -> Result { - let data = match &ast.data { - syn::Data::Struct(data) => match &data.fields { - syn::Fields::Named(fields) => { - let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?; - let builder_creation = struct_info.builder_creation_impl()?; - let conversion_helper = struct_info.conversion_helper_impl(); - let fields = struct_info - .included_fields() - .map(|f| struct_info.field_impl(f)) - .collect::, _>>()?; - let fields = quote!(#(#fields)*).into_iter(); - let required_fields = struct_info - .included_fields() - .filter(|f| f.builder_attr.default.is_none() && f.default_ty.is_none()) - .map(|f| struct_info.required_field_impl(f)) - .collect::>(); - let build_method = struct_info.build_method_impl(); - - quote! { - #builder_creation - #conversion_helper - #( #fields )* - #( #required_fields )* - #build_method - } - } - syn::Fields::Unnamed(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for tuple structs")), - syn::Fields::Unit => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unit structs")), - }, - syn::Data::Enum(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for enums")), - syn::Data::Union(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unions")), - }; - Ok(data) +impl BuilderOptional for () { + fn into_value Option>(self, default: F) -> T { + default().unwrap() + } } -// It'd be nice for the compilation tests to live in tests/ with the rest, but short of pulling in -// some other test runner for that purpose (e.g. compiletest_rs), rustdoc compile_fail in this -// crate is all we can use. - -#[doc(hidden)] -/// When a property is skipped, you can't set it: -/// (“method `y` not found for this”) -/// -/// ```compile_fail -/// use typed_builder::TypedBuilder; -/// -/// #[derive(PartialEq, TypedBuilder)] -/// struct Foo { -/// #[builder(default, setter(skip))] -/// y: i8, -/// } -/// -/// let _ = Foo::builder().y(1i8).build(); -/// ``` -/// -/// But you can build a record: -/// -/// ``` -/// use typed_builder::TypedBuilder; -/// -/// #[derive(PartialEq, TypedBuilder)] -/// struct Foo { -/// #[builder(default, setter(skip))] -/// y: i8, -/// } -/// -/// let _ = Foo::builder().build(); -/// ``` -/// -/// `skip` without `default` is disallowed: -/// (“error: #[builder(skip)] must be accompanied by default”) -/// -/// ```compile_fail -/// use typed_builder::TypedBuilder; -/// -/// #[derive(PartialEq, TypedBuilder)] -/// struct Foo { -/// #[builder(setter(skip))] -/// y: i8, -/// } -/// ``` -/// -/// `clone` does not work if non-Clone fields have already been set -/// -/// ```compile_fail -/// use typed_builder::TypedBuilder; -/// -/// #[derive(Default)] -/// struct Uncloneable; -/// -/// #[derive(TypedBuilder)] -/// struct Foo { -/// x: Uncloneable, -/// y: i32, -/// } -/// -/// let _ = Foo::builder().x(Uncloneable).clone(); -/// ``` -/// -/// Same, but with generics -/// -/// ```compile_fail -/// use typed_builder::TypedBuilder; -/// -/// #[derive(Default)] -/// struct Uncloneable; -/// -/// #[derive(TypedBuilder)] -/// struct Foo { -/// x: T, -/// y: i32, -/// } -/// -/// let _ = Foo::builder().x(Uncloneable).clone(); -/// ``` -fn _compile_fail_tests() {} +impl BuilderOptional for (T,) { + fn into_value Option>(self, _: F) -> T { + self.0 + } +} diff --git a/tests/tests.rs b/tests/tests.rs index 579d9305..3402f71d 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,342 +2,342 @@ use typed_builder::TypedBuilder; -// #[test] -// fn test_simple() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// x: i32, -// y: i32, -// } -// -// assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); -// assert!(Foo::builder().y(1).x(2).build() == Foo { x: 2, y: 1 }); -// } -// -// #[test] -// fn test_lifetime() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo<'a, 'b> { -// x: &'a i32, -// y: &'b i32, -// } -// -// assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); -// } -// -// #[test] -// fn test_lifetime_bounded() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo<'a, 'b: 'a> { -// x: &'a i32, -// y: &'b i32, -// } -// -// assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); -// } -// -// #[test] -// fn test_mutable_borrows() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo<'a, 'b> { -// x: &'a mut i32, -// y: &'b mut i32, -// } -// -// let mut a = 1; -// let mut b = 2; -// { -// let foo = Foo::builder().x(&mut a).y(&mut b).build(); -// *foo.x *= 10; -// *foo.y *= 100; -// } -// assert!(a == 10); -// assert!(b == 200); -// } -// -// #[test] -// fn test_generics() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// x: S, -// y: T, -// } -// -// assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); -// } -// -// #[test] -// fn test_into() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(setter(into))] -// x: i32, -// } -// -// assert!(Foo::builder().x(1_u8).build() == Foo { x: 1 }); -// } -// -// #[test] -// fn test_strip_option_with_into() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(setter(strip_option, into))] -// x: Option, -// } -// -// assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); -// } -// -// #[test] -// fn test_into_with_strip_option() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(setter(into, strip_option))] -// x: Option, -// } -// -// assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); -// } -// -// #[test] -// fn test_strip_bool() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(setter(into, strip_bool))] -// x: bool, -// } -// -// assert!(Foo::builder().x().build() == Foo { x: true }); -// assert!(Foo::builder().build() == Foo { x: false }); -// } -// -// #[test] -// fn test_default() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(default, setter(strip_option))] -// x: Option, -// #[builder(default = 10)] -// y: i32, -// #[builder(default = vec![20, 30, 40])] -// z: Vec, -// } -// -// assert!( -// Foo::builder().build() -// == Foo { -// x: None, -// y: 10, -// z: vec![20, 30, 40] -// } -// ); -// assert!( -// Foo::builder().x(1).build() -// == Foo { -// x: Some(1), -// y: 10, -// z: vec![20, 30, 40] -// } -// ); -// assert!( -// Foo::builder().y(2).build() -// == Foo { -// x: None, -// y: 2, -// z: vec![20, 30, 40] -// } -// ); -// assert!( -// Foo::builder().x(1).y(2).build() -// == Foo { -// x: Some(1), -// y: 2, -// z: vec![20, 30, 40] -// } -// ); -// assert!( -// Foo::builder().z(vec![1, 2, 3]).build() -// == Foo { -// x: None, -// y: 10, -// z: vec![1, 2, 3] -// } -// ); -// } -// -// #[test] -// fn test_field_dependencies_in_build() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(default, setter(strip_option))] -// x: Option, -// #[builder(default = 10)] -// y: i32, -// #[builder(default = vec![y, 30, 40])] -// z: Vec, -// } -// -// assert!( -// Foo::builder().build() -// == Foo { -// x: None, -// y: 10, -// z: vec![10, 30, 40] -// } -// ); -// assert!( -// Foo::builder().x(1).build() -// == Foo { -// x: Some(1), -// y: 10, -// z: vec![10, 30, 40] -// } -// ); -// assert!( -// Foo::builder().y(2).build() -// == Foo { -// x: None, -// y: 2, -// z: vec![2, 30, 40] -// } -// ); -// assert!( -// Foo::builder().x(1).y(2).build() -// == Foo { -// x: Some(1), -// y: 2, -// z: vec![2, 30, 40] -// } -// ); -// assert!( -// Foo::builder().z(vec![1, 2, 3]).build() -// == Foo { -// x: None, -// y: 10, -// z: vec![1, 2, 3] -// } -// ); -// } -// -// // compile-fail tests for skip are in src/lib.rs out of necessity. These are just the bland -// // successful cases. -// #[test] -// fn test_skip() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(default, setter(skip))] -// x: i32, -// #[builder(setter(into))] -// y: i32, -// #[builder(default = y + 1, setter(skip))] -// z: i32, -// } -// -// assert!(Foo::builder().y(1_u8).build() == Foo { x: 0, y: 1, z: 2 }); -// } -// -// #[test] -// fn test_docs() { -// #[derive(TypedBuilder)] -// #[builder( -// builder_method_doc = "Point::builder() method docs", -// builder_type_doc = "PointBuilder type docs", -// build_method_doc = "PointBuilder.build() method docs" -// )] -// struct Point { -// #[allow(dead_code)] -// x: i32, -// #[builder( -// default = x, -// setter( -// doc = "Set `z`. If you don't specify a value it'll default to the value specified for `x`.", -// ), -// )] -// #[allow(dead_code)] -// y: i32, -// } -// -// let _ = Point::builder(); -// } -// -// #[test] -// fn test_builder_name() { -// #[derive(TypedBuilder)] -// struct Foo {} -// -// let _: FooBuilder<_> = Foo::builder(); -// } -// -// // NOTE: `test_builder_type_stability` and `test_builder_type_stability_with_other_generics` are -// // meant to ensure we don't break things for people that use custom `impl`s on the builder -// // type before the tuple field generic param transformation traits are in. -// // See: -// // - https://github.com/idanarye/rust-typed-builder/issues/22 -// // - https://github.com/idanarye/rust-typed-builder/issues/23 -// #[test] -// fn test_builder_type_stability() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// x: i32, -// y: i32, -// z: i32, -// } -// -// impl FooBuilder<((), Y, ())> { -// fn xz(self, x: i32, z: i32) -> FooBuilder<((i32,), Y, (i32,))> { -// self.x(x).z(z) -// } -// } -// -// assert!(Foo::builder().xz(1, 2).y(3).build() == Foo { x: 1, y: 3, z: 2 }); -// assert!(Foo::builder().xz(1, 2).y(3).build() == Foo::builder().x(1).z(2).y(3).build()); -// -// assert!(Foo::builder().y(1).xz(2, 3).build() == Foo { x: 2, y: 1, z: 3 }); -// assert!(Foo::builder().y(1).xz(2, 3).build() == Foo::builder().y(1).x(2).z(3).build()); -// } -// -// #[test] -// fn test_builder_type_stability_with_other_generics() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// x: X, -// y: Y, -// } -// -// impl FooBuilder<((), Y_), X, Y> { -// fn x_default(self) -> FooBuilder<((X,), Y_), X, Y> { -// self.x(X::default()) -// } -// } -// -// assert!(Foo::builder().x_default().y(1.0).build() == Foo { x: 0, y: 1.0 }); -// assert!( -// Foo::builder().y("hello".to_owned()).x_default().build() -// == Foo { -// x: "", -// y: "hello".to_owned() -// } -// ); -// } +#[test] +fn test_simple() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + x: i32, + y: i32, + } + + assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); + assert!(Foo::builder().y(1).x(2).build() == Foo { x: 2, y: 1 }); +} + +#[test] +fn test_lifetime() { + #[derive(PartialEq, TypedBuilder)] + struct Foo<'a, 'b> { + x: &'a i32, + y: &'b i32, + } + + assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); +} + +#[test] +fn test_lifetime_bounded() { + #[derive(PartialEq, TypedBuilder)] + struct Foo<'a, 'b: 'a> { + x: &'a i32, + y: &'b i32, + } + + assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 }); +} + +#[test] +fn test_mutable_borrows() { + #[derive(PartialEq, TypedBuilder)] + struct Foo<'a, 'b> { + x: &'a mut i32, + y: &'b mut i32, + } + + let mut a = 1; + let mut b = 2; + { + let foo = Foo::builder().x(&mut a).y(&mut b).build(); + *foo.x *= 10; + *foo.y *= 100; + } + assert!(a == 10); + assert!(b == 200); +} + +#[test] +fn test_generics() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + x: S, + y: T, + } + + assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 }); +} + +#[test] +fn test_into() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(setter(into))] + x: i32, + } + + assert!(Foo::builder().x(1_u8).build() == Foo { x: 1 }); +} + +#[test] +fn test_strip_option_with_into() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(setter(strip_option, into))] + x: Option, + } + + assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); +} + +#[test] +fn test_into_with_strip_option() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(setter(into, strip_option))] + x: Option, + } + + assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) }); +} + +#[test] +fn test_strip_bool() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(setter(into, strip_bool))] + x: bool, + } + + assert!(Foo::builder().x().build() == Foo { x: true }); + assert!(Foo::builder().build() == Foo { x: false }); +} + +#[test] +fn test_default() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(default, setter(strip_option))] + x: Option, + #[builder(default = 10)] + y: i32, + #[builder(default = vec![20, 30, 40])] + z: Vec, + } + + assert!( + Foo::builder().build() + == Foo { + x: None, + y: 10, + z: vec![20, 30, 40] + } + ); + assert!( + Foo::builder().x(1).build() + == Foo { + x: Some(1), + y: 10, + z: vec![20, 30, 40] + } + ); + assert!( + Foo::builder().y(2).build() + == Foo { + x: None, + y: 2, + z: vec![20, 30, 40] + } + ); + assert!( + Foo::builder().x(1).y(2).build() + == Foo { + x: Some(1), + y: 2, + z: vec![20, 30, 40] + } + ); + assert!( + Foo::builder().z(vec![1, 2, 3]).build() + == Foo { + x: None, + y: 10, + z: vec![1, 2, 3] + } + ); +} + +#[test] +fn test_field_dependencies_in_build() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(default, setter(strip_option))] + x: Option, + #[builder(default = 10)] + y: i32, + #[builder(default = vec![y, 30, 40])] + z: Vec, + } + + assert!( + Foo::builder().build() + == Foo { + x: None, + y: 10, + z: vec![10, 30, 40] + } + ); + assert!( + Foo::builder().x(1).build() + == Foo { + x: Some(1), + y: 10, + z: vec![10, 30, 40] + } + ); + assert!( + Foo::builder().y(2).build() + == Foo { + x: None, + y: 2, + z: vec![2, 30, 40] + } + ); + assert!( + Foo::builder().x(1).y(2).build() + == Foo { + x: Some(1), + y: 2, + z: vec![2, 30, 40] + } + ); + assert!( + Foo::builder().z(vec![1, 2, 3]).build() + == Foo { + x: None, + y: 10, + z: vec![1, 2, 3] + } + ); +} + +// compile-fail tests for skip are in src/lib.rs out of necessity. These are just the bland +// successful cases. +#[test] +fn test_skip() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(default, setter(skip))] + x: i32, + #[builder(setter(into))] + y: i32, + #[builder(default = y + 1, setter(skip))] + z: i32, + } + + assert!(Foo::builder().y(1_u8).build() == Foo { x: 0, y: 1, z: 2 }); +} + +#[test] +fn test_docs() { + #[derive(TypedBuilder)] + #[builder( + builder_method_doc = "Point::builder() method docs", + builder_type_doc = "PointBuilder type docs", + build_method_doc = "PointBuilder.build() method docs" + )] + struct Point { + #[allow(dead_code)] + x: i32, + #[builder( + default = x, + setter( + doc = "Set `z`. If you don't specify a value it'll default to the value specified for `x`.", + ), + )] + #[allow(dead_code)] + y: i32, + } + + let _ = Point::builder(); +} + +#[test] +fn test_builder_name() { + #[derive(TypedBuilder)] + struct Foo {} + + let _: FooBuilder<_> = Foo::builder(); +} + +// NOTE: `test_builder_type_stability` and `test_builder_type_stability_with_other_generics` are +// meant to ensure we don't break things for people that use custom `impl`s on the builder +// type before the tuple field generic param transformation traits are in. +// See: +// - https://github.com/idanarye/rust-typed-builder/issues/22 +// - https://github.com/idanarye/rust-typed-builder/issues/23 +#[test] +fn test_builder_type_stability() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + x: i32, + y: i32, + z: i32, + } + + impl FooBuilder<((), Y, ())> { + fn xz(self, x: i32, z: i32) -> FooBuilder<((i32,), Y, (i32,))> { + self.x(x).z(z) + } + } + + assert!(Foo::builder().xz(1, 2).y(3).build() == Foo { x: 1, y: 3, z: 2 }); + assert!(Foo::builder().xz(1, 2).y(3).build() == Foo::builder().x(1).z(2).y(3).build()); + + assert!(Foo::builder().y(1).xz(2, 3).build() == Foo { x: 2, y: 1, z: 3 }); + assert!(Foo::builder().y(1).xz(2, 3).build() == Foo::builder().y(1).x(2).z(3).build()); +} + +#[test] +fn test_builder_type_stability_with_other_generics() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + x: X, + y: Y, + } + + impl FooBuilder<((), Y_), X, Y> { + fn x_default(self) -> FooBuilder<((X,), Y_), X, Y> { + self.x(X::default()) + } + } + + assert!(Foo::builder().x_default().y(1.0).build() == Foo { x: 0, y: 1.0 }); + assert!( + Foo::builder().y("hello".to_owned()).x_default().build() + == Foo { + x: "", + y: "hello".to_owned() + } + ); +} #[test] #[allow(clippy::items_after_statements)] fn test_builder_type_with_default_on_generic_type() { - // #[derive(PartialEq, TypedBuilder)] - // struct Types { - // x: X, - // y: Y, - // } - // assert!(Types::builder().x(()).y(()).build() == Types { x: (), y: () }); - // - // #[derive(PartialEq, TypedBuilder)] - // struct TypeAndLifetime<'a, X, Y: Default, Z = usize> { - // x: X, - // y: Y, - // z: &'a Z, - // } + #[derive(PartialEq, TypedBuilder)] + struct Types { + x: X, + y: Y, + } + assert!(Types::builder().x(()).y(()).build() == Types { x: (), y: () }); + + #[derive(PartialEq, TypedBuilder)] + struct TypeAndLifetime<'a, X, Y: Default, Z> { + x: X, + y: Y, + z: &'a Z, + } let a = 0; - // assert!(TypeAndLifetime::builder().x(()).y(0).z(&a).build() == TypeAndLifetime { x: (), y: 0, z: &0 }); + assert!(TypeAndLifetime::builder().x(()).y(0).z(&a).build() == TypeAndLifetime { x: (), y: 0, z: &0 }); #[derive(PartialEq, TypedBuilder)] struct Foo<'a, X, Y: Default, Z: Default = usize, M = ()> { @@ -360,7 +360,7 @@ fn test_builder_type_with_default_on_generic_type() { } // compile test if rustc can infer type for `z` and `m` - Foo::<(), _, _, f64>::builder().x(()).y(&a).z_default().m(1.0).build(); + Foo::<(), _, _, _>::builder().x(()).y(&a).z_default().m(1.0f64).build(); Foo::<(), _, _, _>::builder().x(()).y(&a).z_default().m_default().build(); assert!( @@ -383,211 +383,221 @@ fn test_builder_type_with_default_on_generic_type() { ); } -// #[test] -// fn test_builder_type_skip_into() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// x: X, -// } -// -// // compile test if rustc can infer type for `x` -// Foo::builder().x(()).build(); -// -// assert!(Foo::builder().x(()).build() == Foo { x: () }); -// } -// -// #[test] -// fn test_default_code() { -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(default_code = "\"text1\".to_owned()")] -// x: String, -// -// #[builder(default_code = r#""text2".to_owned()"#)] -// y: String, -// } -// -// assert!( -// Foo::builder().build() -// == Foo { -// x: "text1".to_owned(), -// y: "text2".to_owned() -// } -// ); -// } -// -// #[test] -// fn test_field_defaults_default_value() { -// #[derive(PartialEq, TypedBuilder)] -// #[builder(field_defaults(default = 12))] -// struct Foo { -// x: i32, -// #[builder(!default)] -// y: String, -// #[builder(default = 13)] -// z: i32, -// } -// -// assert!( -// Foo::builder().y("bla".to_owned()).build() -// == Foo { -// x: 12, -// y: "bla".to_owned(), -// z: 13 -// } -// ); -// } -// -// #[test] -// fn test_field_defaults_setter_options() { -// #[derive(PartialEq, TypedBuilder)] -// #[builder(field_defaults(setter(strip_option)))] -// struct Foo { -// x: Option, -// #[builder(setter(!strip_option))] -// y: i32, -// } -// -// assert!(Foo::builder().x(1).y(2).build() == Foo { x: Some(1), y: 2 }); -// } -// -// #[test] -// fn test_clone_builder() { -// #[derive(PartialEq, Default)] -// struct Uncloneable; -// -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// x: i32, -// y: i32, -// #[builder(default)] -// z: Uncloneable, -// } -// -// let semi_built = Foo::builder().x(1); -// -// assert!( -// semi_built.clone().y(2).build() -// == Foo { -// x: 1, -// y: 2, -// z: Uncloneable -// } -// ); -// assert!( -// semi_built.y(3).build() -// == Foo { -// x: 1, -// y: 3, -// z: Uncloneable -// } -// ); -// } -// -// #[test] -// #[allow(clippy::items_after_statements)] -// fn test_clone_builder_with_generics() { -// #[derive(PartialEq, Default)] -// struct Uncloneable; -// -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// x: T, -// y: i32, -// } -// -// let semi_built1 = Foo::builder().x(1); -// -// assert!(semi_built1.clone().y(2).build() == Foo { x: 1, y: 2 }); -// assert!(semi_built1.y(3).build() == Foo { x: 1, y: 3 }); -// -// let semi_built2 = Foo::builder().x("four"); -// -// assert!(semi_built2.clone().y(5).build() == Foo { x: "four", y: 5 }); -// assert!(semi_built2.clone().y(6).build() == Foo { x: "four", y: 6 }); -// -// // Just to make sure it can build with generic bounds -// #[allow(dead_code)] -// #[derive(TypedBuilder)] -// struct Bar -// where -// T: std::fmt::Display, -// { -// x: T, -// } -// } -// -// #[test] -// fn test_builder_on_struct_with_keywords() { -// #[allow(non_camel_case_types)] -// #[derive(PartialEq, TypedBuilder)] -// struct r#struct { -// r#fn: u32, -// #[builder(default, setter(strip_option))] -// r#type: Option, -// #[builder(default = Some(()), setter(skip))] -// r#enum: Option<()>, -// #[builder(setter(into))] -// r#union: String, -// } -// -// assert!( -// r#struct::builder().r#fn(1).r#union("two").build() -// == r#struct { -// r#fn: 1, -// r#type: None, -// r#enum: Some(()), -// r#union: "two".to_owned(), -// } -// ); -// } -// -// #[test] -// fn test_field_setter_transform() { -// #[derive(PartialEq)] -// struct Point { -// x: i32, -// y: i32, -// } -// -// #[derive(PartialEq, TypedBuilder)] -// struct Foo { -// #[builder(setter(transform = |x: i32, y: i32| Point { x, y }))] -// point: Point, -// } -// -// assert!( -// Foo::builder().point(1, 2).build() -// == Foo { -// point: Point { x: 1, y: 2 } -// } -// ); -// } -// -// #[test] -// fn test_build_method() { -// #[derive(PartialEq, TypedBuilder)] -// #[builder(build_method(vis="", name=__build))] -// struct Foo { -// x: i32, -// } -// -// assert!(Foo::builder().x(1).__build() == Foo { x: 1 }); -// } -// -// #[test] -// fn test_struct_generic_defaults() { -// #[derive(TypedBuilder)] -// pub struct Props<'a, OnInput: FnOnce(usize) -> usize = Box usize>> { -// #[builder(default, setter(into))] -// pub class: Option<&'a str>, -// pub label: &'a str, -// #[builder(setter(into))] -// pub on_input: Option, -// } -// -// let props = Props::builder().label("label").on_input(|x: usize| x).build(); -// assert_eq!(props.class, None); -// assert_eq!(props.label, "label"); -// assert_eq!((props.on_input.unwrap())(123), 123); -// } +#[test] +fn test_builder_type_skip_into() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + x: X, + } + + // compile test if rustc can infer type for `x` + Foo::builder().x(()).build(); + + assert!(Foo::builder().x(()).build() == Foo { x: () }); +} + +#[test] +fn test_default_code() { + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(default_code = "\"text1\".to_owned()")] + x: String, + + #[builder(default_code = r#""text2".to_owned()"#)] + y: String, + } + + assert!( + Foo::builder().build() + == Foo { + x: "text1".to_owned(), + y: "text2".to_owned() + } + ); +} + +#[test] +fn test_field_defaults_default_value() { + #[derive(PartialEq, TypedBuilder)] + #[builder(field_defaults(default = 12))] + struct Foo { + x: i32, + #[builder(!default)] + y: String, + #[builder(default = 13)] + z: i32, + } + + assert!( + Foo::builder().y("bla".to_owned()).build() + == Foo { + x: 12, + y: "bla".to_owned(), + z: 13 + } + ); +} + +#[test] +fn test_field_defaults_setter_options() { + #[derive(PartialEq, TypedBuilder)] + #[builder(field_defaults(setter(strip_option)))] + struct Foo { + x: Option, + #[builder(setter(!strip_option))] + y: i32, + } + + assert!(Foo::builder().x(1).y(2).build() == Foo { x: Some(1), y: 2 }); +} + +#[test] +fn test_clone_builder() { + #[derive(PartialEq, Default)] + struct Uncloneable; + + #[derive(PartialEq, TypedBuilder)] + struct Foo { + x: i32, + y: i32, + #[builder(default)] + z: Uncloneable, + } + + let semi_built = Foo::builder().x(1); + + assert!( + semi_built.clone().y(2).build() + == Foo { + x: 1, + y: 2, + z: Uncloneable + } + ); + assert!( + semi_built.y(3).build() + == Foo { + x: 1, + y: 3, + z: Uncloneable + } + ); +} + +#[test] +#[allow(clippy::items_after_statements)] +fn test_clone_builder_with_generics() { + #[derive(PartialEq, Default)] + struct Uncloneable; + + #[derive(PartialEq, TypedBuilder)] + struct Foo { + x: T, + y: i32, + } + + let semi_built1 = Foo::builder().x(1); + + assert!(semi_built1.clone().y(2).build() == Foo { x: 1, y: 2 }); + assert!(semi_built1.y(3).build() == Foo { x: 1, y: 3 }); + + let semi_built2 = Foo::builder().x("four"); + + assert!(semi_built2.clone().y(5).build() == Foo { x: "four", y: 5 }); + assert!(semi_built2.clone().y(6).build() == Foo { x: "four", y: 6 }); + + // Just to make sure it can build with generic bounds + #[allow(dead_code)] + #[derive(TypedBuilder)] + struct Bar + where + T: std::fmt::Display, + { + x: T, + } +} + +#[test] +fn test_builder_on_struct_with_keywords() { + #[allow(non_camel_case_types)] + #[derive(PartialEq, TypedBuilder)] + struct r#struct { + r#fn: u32, + #[builder(default, setter(strip_option))] + r#type: Option, + #[builder(default = Some(()), setter(skip))] + r#enum: Option<()>, + #[builder(setter(into))] + r#union: String, + } + + assert!( + r#struct::builder().r#fn(1).r#union("two").build() + == r#struct { + r#fn: 1, + r#type: None, + r#enum: Some(()), + r#union: "two".to_owned(), + } + ); +} + +#[test] +fn test_field_setter_transform() { + #[derive(PartialEq)] + struct Point { + x: i32, + y: i32, + } + + #[derive(PartialEq, TypedBuilder)] + struct Foo { + #[builder(setter(transform = |x: i32, y: i32| Point { x, y }))] + point: Point, + } + + assert!( + Foo::builder().point(1, 2).build() + == Foo { + point: Point { x: 1, y: 2 } + } + ); +} + +#[test] +fn test_build_method() { + #[derive(PartialEq, TypedBuilder)] + #[builder(build_method(vis="", name=__build))] + struct Foo { + x: i32, + } + + assert!(Foo::builder().x(1).__build() == Foo { x: 1 }); +} + +#[test] +fn test_struct_generic_defaults() { + #[derive(TypedBuilder)] + pub struct Props<'a, OnInput: FnOnce(usize) -> usize = Box usize>> { + #[builder(default, setter(into))] + pub class: Option<&'a str>, + pub label: &'a str, + #[builder(setter(strip_option))] + pub on_input: Option, + } + + let _: PropsBuilder<((), (), (Option usize>>,)), Box usize>> = Props::builder(); + + let props = Props::builder().label("label").on_input(|x: usize| x).build(); + assert_eq!(props.class, None); + assert_eq!(props.label, "label"); + assert_eq!((props.on_input.unwrap())(123), 123); + + #[derive(TypedBuilder)] + struct Foo { + #[builder(default = 12)] + x: T, + } + + assert_eq!(Foo::builder().build().x, 12); +} From 8c0be3e8773fba3831d98c860c5598a8f64ff7ee Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 03:49:46 -0800 Subject: [PATCH 03/11] reduce semver strictness on new dependencies --- proc-macros/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index d83b9a6b..7544b54a 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -15,11 +15,11 @@ categories = ["rust-patterns"] proc-macro = true [dependencies] -either = "1.8.0" -prettyplease = { version = "*", optional = true } +either = "1" +prettyplease = { version = "0.1", optional = true } proc-macro2 = "1" quote = "1" -regex = "1.7.0" +regex = "1" syn = { version = "1", features = ["full", "extra-traits"] } [features] From 891dcbfeb3788aab44253121eec1510e75c7fcce Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 04:03:44 -0800 Subject: [PATCH 04/11] add usage of proc-macro-crate to support package renames in applications using this library --- proc-macros/Cargo.toml | 1 + proc-macros/src/struct_info.rs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index 7544b54a..21f122a8 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -18,6 +18,7 @@ proc-macro = true either = "1" prettyplease = { version = "0.1", optional = true } proc-macro2 = "1" +proc-macro-crate = "1" quote = "1" regex = "1" syn = { version = "1", features = ["full", "extra-traits"] } diff --git a/proc-macros/src/struct_info.rs b/proc-macros/src/struct_info.rs index 711ac651..789bc2fc 100644 --- a/proc-macros/src/struct_info.rs +++ b/proc-macros/src/struct_info.rs @@ -1,5 +1,6 @@ use either::Either::*; use proc_macro2::TokenStream; +use proc_macro_crate::{crate_name, FoundCrate}; use quote::{format_ident, quote}; use regex::Regex; use std::iter::FromIterator; @@ -688,6 +689,15 @@ impl<'a> StructInfo<'a> { let descructuring = self.included_fields().map(|f| f.name); + let found_crate = crate_name("typed-builder").expect("typed-builder is present in `Cargo.toml`"); + let crate_name = match found_crate { + FoundCrate::Itself => quote!(typed_builder), + FoundCrate::Name(name) => { + let ident = syn::Ident::new(&name, proc_macro2::Span::call_site()); + quote!(#ident) + } + }; + // The default of a field can refer to earlier-defined fields, which we handle by // writing out a bunch of `let` statements first, which can each refer to earlier ones. // This means that field ordering may actually be significant, which isn't ideal. We could @@ -699,9 +709,9 @@ impl<'a> StructInfo<'a> { if field.builder_attr.setter.skip.is_some() { quote!(let #name = #default;) } else if !field.used_default_generic_idents.is_empty() { - quote!(let #name = typed_builder::BuilderOptional::into_value(#name, || None);) + quote!(let #name = #crate_name::BuilderOptional::into_value(#name, || None);) } else { - quote!(let #name = typed_builder::BuilderOptional::into_value(#name, || Some(#default));) + quote!(let #name = #crate_name::BuilderOptional::into_value(#name, || Some(#default));) } } else { quote!(let #name = #name.0;) From 4094904377dcc90e4ef84f6e9aafa501a070ae17 Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 04:16:51 -0800 Subject: [PATCH 05/11] add example of working support for issue 44 --- examples/default_generics.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/default_generics.rs b/examples/default_generics.rs index d04df786..d4fdb1cc 100644 --- a/examples/default_generics.rs +++ b/examples/default_generics.rs @@ -9,9 +9,17 @@ pub struct Props<'a, OnInput: FnOnce(usize) -> usize = Box pub on_input: Option, } +#[derive(TypedBuilder)] +struct Foo { + #[builder(default = 12)] + x: T, +} + fn main() { let props = Props::builder().label("label").on_input(|x: usize| x).build(); assert_eq!(props.class, None); assert_eq!(props.label, "label"); assert_eq!((props.on_input.unwrap())(123), 123); + + assert_eq!(Foo::builder().build().x, 12); } From 9e5ee227bac36f0caf4fcf976f7bc32a2d4c6745 Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 04:28:13 -0800 Subject: [PATCH 06/11] undo refactor of moving proc-macros into separate crate --- Cargo.toml | 12 +- examples/complicate_build.rs | 4 +- proc-macros/Cargo.toml | 27 --- proc-macros/src/lib.rs | 285 ----------------------- {proc-macros/src => src}/field_info.rs | 0 src/lib.rs | 294 +++++++++++++++++++++++- {proc-macros/src => src}/struct_info.rs | 72 +++--- {proc-macros/src => src}/util.rs | 0 8 files changed, 335 insertions(+), 359 deletions(-) delete mode 100644 proc-macros/Cargo.toml delete mode 100644 proc-macros/src/lib.rs rename {proc-macros/src => src}/field_info.rs (100%) rename {proc-macros/src => src}/struct_info.rs (95%) rename {proc-macros/src => src}/util.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index ae1805be..1d15f67f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,16 @@ readme = "README.md" keywords = ["builder"] categories = ["rust-patterns"] +[lib] +proc-macro = true + [dependencies] -typed-builder-proc-macros.path = "proc-macros" +either = "1" +prettyplease = { version = "0.1", optional = true } +proc-macro2 = "1" +quote = "1" +regex = "1" +syn = { version = "1", features = ["full", "extra-traits"] } [features] -debug = ["typed-builder-proc-macros/debug"] +debug = ["dep:prettyplease"] diff --git a/examples/complicate_build.rs b/examples/complicate_build.rs index 8c6ecd23..50bbf109 100644 --- a/examples/complicate_build.rs +++ b/examples/complicate_build.rs @@ -1,5 +1,5 @@ mod scope { - use typed_builder::{BuilderOptional, TypedBuilder}; + use typed_builder::TypedBuilder; #[derive(PartialEq, TypedBuilder)] #[builder(build_method(vis="", name=__build))] @@ -25,7 +25,7 @@ mod scope { // We can use `cargo expand` to show code expanded by `TypedBuilder`, // copy the generated `__build` method, and modify the content of the build method. #[allow(non_camel_case_types)] - impl<__z: BuilderOptional, __y: BuilderOptional>> FooBuilder<((i32,), __y, __z)> { + impl<__z: FooBuilder_Optional, __y: FooBuilder_Optional>> FooBuilder<((i32,), __y, __z)> { pub fn build(self) -> Bar { let foo = self.__build(); Bar { diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml deleted file mode 100644 index 21f122a8..00000000 --- a/proc-macros/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "typed-builder-proc-macros" -description = "Compile-time type-checked builder derive" -version = "0.11.0" -authors = ["IdanArye ", "Chris Morgan "] -edition = "2018" -license = "MIT/Apache-2.0" -repository = "https://github.com/idanarye/rust-typed-builder" -documentation = "https://idanarye.github.io/rust-typed-builder/" -readme = "README.md" -keywords = ["builder"] -categories = ["rust-patterns"] - -[lib] -proc-macro = true - -[dependencies] -either = "1" -prettyplease = { version = "0.1", optional = true } -proc-macro2 = "1" -proc-macro-crate = "1" -quote = "1" -regex = "1" -syn = { version = "1", features = ["full", "extra-traits"] } - -[features] -debug = ["dep:prettyplease"] diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs deleted file mode 100644 index dc09781c..00000000 --- a/proc-macros/src/lib.rs +++ /dev/null @@ -1,285 +0,0 @@ -extern crate proc_macro; // Needed even though it's the 2018 edition. See https://github.com/idanarye/rust-typed-builder/issues/57. - -use proc_macro2::TokenStream; - -use syn::parse::Error; -use syn::spanned::Spanned; -use syn::{parse_macro_input, DeriveInput}; - -use quote::quote; - -mod field_info; -mod struct_info; -mod util; - -/// `TypedBuilder` is not a real type - deriving it will generate a `::builder()` method on your -/// struct that will return a compile-time checked builder. Set the fields using setters with the -/// same name as the struct's fields and call `.build()` when you are done to create your object. -/// -/// Trying to set the same fields twice will generate a compile-time error. Trying to build without -/// setting one of the fields will also generate a compile-time error - unless that field is marked -/// as `#[builder(default)]`, in which case the `::default()` value of it's type will be picked. If -/// you want to set a different default, use `#[builder(default=...)]`. -/// -/// # Examples -/// -/// ``` -/// use typed_builder::TypedBuilder; -/// -/// #[derive(PartialEq, TypedBuilder)] -/// struct Foo { -/// // Mandatory Field: -/// x: i32, -/// -/// // #[builder(default)] without parameter - use the type's default -/// // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` -/// #[builder(default, setter(strip_option))] -/// y: Option, -/// -/// // Or you can set the default -/// #[builder(default=20)] -/// z: i32, -/// } -/// -/// assert!( -/// Foo::builder().x(1).y(2).z(3).build() -/// == Foo { x: 1, y: Some(2), z: 3, }); -/// -/// // Change the order of construction: -/// assert!( -/// Foo::builder().z(1).x(2).y(3).build() -/// == Foo { x: 2, y: Some(3), z: 1, }); -/// -/// // Optional fields are optional: -/// assert!( -/// Foo::builder().x(1).build() -/// == Foo { x: 1, y: None, z: 20, }); -/// -/// // This will not compile - because we did not set x: -/// // Foo::builder().build(); -/// -/// // This will not compile - because we set y twice: -/// // Foo::builder().x(1).y(2).y(3); -/// ``` -/// -/// # Customisation with attributes -/// -/// In addition to putting `#[derive(TypedBuilder)]` on a type, you can specify a `#[builder(…)]` -/// attribute on the type, and on any fields in it. -/// -/// On the **type**, the following values are permitted: -/// -/// - `doc`: enable documentation of the builder type. By default, the builder type is given -/// `#[doc(hidden)]`, so that the `builder()` method will show `FooBuilder` as its return type, -/// but it won't be a link. If you turn this on, the builder type and its `build` method will get -/// sane defaults. The field methods on the builder will be undocumented by default. -/// -/// - `build_method(...)`: customize the final build method -/// - `vis = "…"`: sets the visibility of the build method, default is `pub` -/// - `name = …`: sets the fn name of the build method, default is `build` -/// -/// - `builder_method_doc = "…"` replaces the default documentation that will be generated for the -/// `builder()` method of the type for which the builder is being generated. -/// -/// - `builder_type_doc = "…"` replaces the default documentation that will be generated for the -/// builder type. Setting this implies `doc`. -/// -/// - `build_method_doc = "…"` replaces the default documentation that will be generated for the -/// `build()` method of the builder type. Setting this implies `doc`. -/// -/// - `field_defaults(...)` is structured like the `#[builder(...)]` attribute you can put on the -/// fields and sets default options for fields of the type. If specific field need to revert some -/// options to the default defaults they can prepend `!` to the option they need to revert, and -/// it would ignore the field defaults for that option in that field. -/// -/// ``` -/// use typed_builder::TypedBuilder; -/// -/// #[derive(TypedBuilder)] -/// #[builder(field_defaults(default, setter(strip_option)))] -/// struct Foo { -/// // Defaults to None, options-stripping is performed: -/// x: Option, -/// -/// // Defaults to 0, option-stripping is not performed: -/// #[builder(setter(!strip_option))] -/// y: i32, -/// -/// // Defaults to Some(13), option-stripping is performed: -/// #[builder(default = Some(13))] -/// z: Option, -/// -/// // Accepts params `(x: f32, y: f32)` -/// #[builder(setter(!strip_option, transform = |x: f32, y: f32| Point { x, y }))] -/// w: Point, -/// } -/// -/// #[derive(Default)] -/// struct Point { x: f32, y: f32 } -/// ``` -/// -/// On each **field**, the following values are permitted: -/// -/// - `default`: make the field optional, defaulting to `Default::default()`. This requires that -/// the field type implement `Default`. Mutually exclusive with any other form of default. -/// -/// - `default = …`: make the field optional, defaulting to the expression `…`. -/// -/// - `default_code = "…"`: make the field optional, defaulting to the expression `…`. Note that -/// you need to enclose it in quotes, which allows you to use it together with other custom -/// derive proc-macro crates that complain about "expected literal". -/// Note that if `...` contains a string, you can use raw string literals to avoid escaping the -/// double quotes - e.g. `#[builder(default_code = r#""default text".to_owned()"#)]`. -/// -/// - `setter(...)`: settings for the field setters. The following values are permitted inside: -/// -/// - `doc = "…"`: sets the documentation for the field's setter on the builder type. This will be -/// of no value unless you enable docs for the builder type with `#[builder(doc)]` or similar on -/// the type. -/// -/// - `skip`: do not define a method on the builder for this field. This requires that a default -/// be set. -/// -/// - `into`: automatically convert the argument of the setter method to the type of the field. -/// Note that this conversion interferes with Rust's type inference and integer literal -/// detection, so this may reduce ergonomics if the field type is generic or an unsigned integer. -/// -/// - `strip_option`: for `Option<...>` fields only, this makes the setter wrap its argument with -/// `Some(...)`, relieving the caller from having to do this. Note that with this setting on -/// one cannot set the field to `None` with the setter - so the only way to get it to be `None` -/// is by using `#[builder(default)]` and not calling the field's setter. -/// -/// - `strip_bool`: for `bool` fields only, this makes the setter receive no arguments and simply -/// set the field's value to `true`. When used, the `default` is automatically set to `false`. -/// -/// - `transform = |param1: Type1, param2: Type2 ...| expr`: this makes the setter accept -/// `param1: Type1, param2: Type2 ...` instead of the field type itself. The parameters are -/// transformed into the field type using the expression `expr`. The transformation is performed -/// when the setter is called. -#[proc_macro_derive(TypedBuilder, attributes(builder))] -pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let tokens: proc_macro::TokenStream = match impl_my_derive(&input) { - Ok(output) => output.into(), - Err(error) => error.to_compile_error().into(), - }; - #[cfg(feature = "debug")] - println!("{}", prettyplease::unparse(&syn::parse_file(&format!("{tokens}")).unwrap())); - tokens -} - -fn impl_my_derive(ast: &syn::DeriveInput) -> Result { - let data = match &ast.data { - syn::Data::Struct(data) => match &data.fields { - syn::Fields::Named(fields) => { - let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?; - let builder_creation = struct_info.builder_creation_impl()?; - let fields = struct_info - .included_fields() - .map(|f| struct_info.field_impl(f)) - .collect::, _>>()?; - let fields = quote!(#(#fields)*).into_iter(); - let required_fields = struct_info - .included_fields() - .filter(|f| f.builder_attr.default.is_none() && f.default_ty.is_none()) - .map(|f| struct_info.required_field_impl(f)) - .collect::>(); - let build_method = struct_info.build_method_impl(); - - quote! { - #builder_creation - #( #fields )* - #( #required_fields )* - #build_method - } - } - syn::Fields::Unnamed(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for tuple structs")), - syn::Fields::Unit => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unit structs")), - }, - syn::Data::Enum(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for enums")), - syn::Data::Union(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unions")), - }; - Ok(data) -} - -// It'd be nice for the compilation tests to live in tests/ with the rest, but short of pulling in -// some other test runner for that purpose (e.g. compiletest_rs), rustdoc compile_fail in this -// crate is all we can use. - -#[doc(hidden)] -/// When a property is skipped, you can't set it: -/// (“method `y` not found for this”) -/// -/// ```compile_fail -/// use typed_builder::TypedBuilder; -/// -/// #[derive(PartialEq, TypedBuilder)] -/// struct Foo { -/// #[builder(default, setter(skip))] -/// y: i8, -/// } -/// -/// let _ = Foo::builder().y(1i8).build(); -/// ``` -/// -/// But you can build a record: -/// -/// ``` -/// use typed_builder::TypedBuilder; -/// -/// #[derive(PartialEq, TypedBuilder)] -/// struct Foo { -/// #[builder(default, setter(skip))] -/// y: i8, -/// } -/// -/// let _ = Foo::builder().build(); -/// ``` -/// -/// `skip` without `default` is disallowed: -/// (“error: #[builder(skip)] must be accompanied by default”) -/// -/// ```compile_fail -/// use typed_builder::TypedBuilder; -/// -/// #[derive(PartialEq, TypedBuilder)] -/// struct Foo { -/// #[builder(setter(skip))] -/// y: i8, -/// } -/// ``` -/// -/// `clone` does not work if non-Clone fields have already been set -/// -/// ```compile_fail -/// use typed_builder::TypedBuilder; -/// -/// #[derive(Default)] -/// struct Uncloneable; -/// -/// #[derive(TypedBuilder)] -/// struct Foo { -/// x: Uncloneable, -/// y: i32, -/// } -/// -/// let _ = Foo::builder().x(Uncloneable).clone(); -/// ``` -/// -/// Same, but with generics -/// -/// ```compile_fail -/// use typed_builder::TypedBuilder; -/// -/// #[derive(Default)] -/// struct Uncloneable; -/// -/// #[derive(TypedBuilder)] -/// struct Foo { -/// x: T, -/// y: i32, -/// } -/// -/// let _ = Foo::builder().x(Uncloneable).clone(); -/// ``` -fn _compile_fail_tests() {} diff --git a/proc-macros/src/field_info.rs b/src/field_info.rs similarity index 100% rename from proc-macros/src/field_info.rs rename to src/field_info.rs diff --git a/src/lib.rs b/src/lib.rs index 02b57c77..793c1c9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,287 @@ -pub use typed_builder_proc_macros::*; +extern crate proc_macro; // Needed even though it's the 2018 edition. See https://github.com/idanarye/rust-typed-builder/issues/57. -pub trait BuilderOptional { - fn into_value Option>(self, default: F) -> T; -} +use proc_macro2::TokenStream; + +use syn::parse::Error; +use syn::spanned::Spanned; +use syn::{parse_macro_input, DeriveInput}; + +use quote::quote; + +mod field_info; +mod struct_info; +mod util; -impl BuilderOptional for () { - fn into_value Option>(self, default: F) -> T { - default().unwrap() - } +/// `TypedBuilder` is not a real type - deriving it will generate a `::builder()` method on your +/// struct that will return a compile-time checked builder. Set the fields using setters with the +/// same name as the struct's fields and call `.build()` when you are done to create your object. +/// +/// Trying to set the same fields twice will generate a compile-time error. Trying to build without +/// setting one of the fields will also generate a compile-time error - unless that field is marked +/// as `#[builder(default)]`, in which case the `::default()` value of it's type will be picked. If +/// you want to set a different default, use `#[builder(default=...)]`. +/// +/// # Examples +/// +/// ``` +/// use typed_builder::TypedBuilder; +/// +/// #[derive(PartialEq, TypedBuilder)] +/// struct Foo { +/// // Mandatory Field: +/// x: i32, +/// +/// // #[builder(default)] without parameter - use the type's default +/// // #[builder(setter(strip_option))] - wrap the setter argument with `Some(...)` +/// #[builder(default, setter(strip_option))] +/// y: Option, +/// +/// // Or you can set the default +/// #[builder(default=20)] +/// z: i32, +/// } +/// +/// assert!( +/// Foo::builder().x(1).y(2).z(3).build() +/// == Foo { x: 1, y: Some(2), z: 3, }); +/// +/// // Change the order of construction: +/// assert!( +/// Foo::builder().z(1).x(2).y(3).build() +/// == Foo { x: 2, y: Some(3), z: 1, }); +/// +/// // Optional fields are optional: +/// assert!( +/// Foo::builder().x(1).build() +/// == Foo { x: 1, y: None, z: 20, }); +/// +/// // This will not compile - because we did not set x: +/// // Foo::builder().build(); +/// +/// // This will not compile - because we set y twice: +/// // Foo::builder().x(1).y(2).y(3); +/// ``` +/// +/// # Customisation with attributes +/// +/// In addition to putting `#[derive(TypedBuilder)]` on a type, you can specify a `#[builder(…)]` +/// attribute on the type, and on any fields in it. +/// +/// On the **type**, the following values are permitted: +/// +/// - `doc`: enable documentation of the builder type. By default, the builder type is given +/// `#[doc(hidden)]`, so that the `builder()` method will show `FooBuilder` as its return type, +/// but it won't be a link. If you turn this on, the builder type and its `build` method will get +/// sane defaults. The field methods on the builder will be undocumented by default. +/// +/// - `build_method(...)`: customize the final build method +/// - `vis = "…"`: sets the visibility of the build method, default is `pub` +/// - `name = …`: sets the fn name of the build method, default is `build` +/// +/// - `builder_method_doc = "…"` replaces the default documentation that will be generated for the +/// `builder()` method of the type for which the builder is being generated. +/// +/// - `builder_type_doc = "…"` replaces the default documentation that will be generated for the +/// builder type. Setting this implies `doc`. +/// +/// - `build_method_doc = "…"` replaces the default documentation that will be generated for the +/// `build()` method of the builder type. Setting this implies `doc`. +/// +/// - `field_defaults(...)` is structured like the `#[builder(...)]` attribute you can put on the +/// fields and sets default options for fields of the type. If specific field need to revert some +/// options to the default defaults they can prepend `!` to the option they need to revert, and +/// it would ignore the field defaults for that option in that field. +/// +/// ``` +/// use typed_builder::TypedBuilder; +/// +/// #[derive(TypedBuilder)] +/// #[builder(field_defaults(default, setter(strip_option)))] +/// struct Foo { +/// // Defaults to None, options-stripping is performed: +/// x: Option, +/// +/// // Defaults to 0, option-stripping is not performed: +/// #[builder(setter(!strip_option))] +/// y: i32, +/// +/// // Defaults to Some(13), option-stripping is performed: +/// #[builder(default = Some(13))] +/// z: Option, +/// +/// // Accepts params `(x: f32, y: f32)` +/// #[builder(setter(!strip_option, transform = |x: f32, y: f32| Point { x, y }))] +/// w: Point, +/// } +/// +/// #[derive(Default)] +/// struct Point { x: f32, y: f32 } +/// ``` +/// +/// On each **field**, the following values are permitted: +/// +/// - `default`: make the field optional, defaulting to `Default::default()`. This requires that +/// the field type implement `Default`. Mutually exclusive with any other form of default. +/// +/// - `default = …`: make the field optional, defaulting to the expression `…`. +/// +/// - `default_code = "…"`: make the field optional, defaulting to the expression `…`. Note that +/// you need to enclose it in quotes, which allows you to use it together with other custom +/// derive proc-macro crates that complain about "expected literal". +/// Note that if `...` contains a string, you can use raw string literals to avoid escaping the +/// double quotes - e.g. `#[builder(default_code = r#""default text".to_owned()"#)]`. +/// +/// - `setter(...)`: settings for the field setters. The following values are permitted inside: +/// +/// - `doc = "…"`: sets the documentation for the field's setter on the builder type. This will be +/// of no value unless you enable docs for the builder type with `#[builder(doc)]` or similar on +/// the type. +/// +/// - `skip`: do not define a method on the builder for this field. This requires that a default +/// be set. +/// +/// - `into`: automatically convert the argument of the setter method to the type of the field. +/// Note that this conversion interferes with Rust's type inference and integer literal +/// detection, so this may reduce ergonomics if the field type is generic or an unsigned integer. +/// +/// - `strip_option`: for `Option<...>` fields only, this makes the setter wrap its argument with +/// `Some(...)`, relieving the caller from having to do this. Note that with this setting on +/// one cannot set the field to `None` with the setter - so the only way to get it to be `None` +/// is by using `#[builder(default)]` and not calling the field's setter. +/// +/// - `strip_bool`: for `bool` fields only, this makes the setter receive no arguments and simply +/// set the field's value to `true`. When used, the `default` is automatically set to `false`. +/// +/// - `transform = |param1: Type1, param2: Type2 ...| expr`: this makes the setter accept +/// `param1: Type1, param2: Type2 ...` instead of the field type itself. The parameters are +/// transformed into the field type using the expression `expr`. The transformation is performed +/// when the setter is called. +#[proc_macro_derive(TypedBuilder, attributes(builder))] +pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let tokens: proc_macro::TokenStream = match impl_my_derive(&input) { + Ok(output) => output.into(), + Err(error) => error.to_compile_error().into(), + }; + #[cfg(feature = "debug")] + println!("{}", prettyplease::unparse(&syn::parse_file(&format!("{tokens}")).unwrap())); + tokens } -impl BuilderOptional for (T,) { - fn into_value Option>(self, _: F) -> T { - self.0 - } +fn impl_my_derive(ast: &syn::DeriveInput) -> Result { + let data = match &ast.data { + syn::Data::Struct(data) => match &data.fields { + syn::Fields::Named(fields) => { + let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?; + let builder_creation = struct_info.builder_creation_impl()?; + let conversion_helper = struct_info.conversion_helper_impl(); + let fields = struct_info + .included_fields() + .map(|f| struct_info.field_impl(f)) + .collect::, _>>()?; + let fields = quote!(#(#fields)*).into_iter(); + let required_fields = struct_info + .included_fields() + .filter(|f| f.builder_attr.default.is_none() && f.default_ty.is_none()) + .map(|f| struct_info.required_field_impl(f)) + .collect::>(); + let build_method = struct_info.build_method_impl(); + + quote! { + #builder_creation + #conversion_helper + #( #fields )* + #( #required_fields )* + #build_method + } + } + syn::Fields::Unnamed(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for tuple structs")), + syn::Fields::Unit => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unit structs")), + }, + syn::Data::Enum(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for enums")), + syn::Data::Union(_) => return Err(Error::new(ast.span(), "TypedBuilder is not supported for unions")), + }; + Ok(data) } + +// It'd be nice for the compilation tests to live in tests/ with the rest, but short of pulling in +// some other test runner for that purpose (e.g. compiletest_rs), rustdoc compile_fail in this +// crate is all we can use. + +#[doc(hidden)] +/// When a property is skipped, you can't set it: +/// (“method `y` not found for this”) +/// +/// ```compile_fail +/// use typed_builder::TypedBuilder; +/// +/// #[derive(PartialEq, TypedBuilder)] +/// struct Foo { +/// #[builder(default, setter(skip))] +/// y: i8, +/// } +/// +/// let _ = Foo::builder().y(1i8).build(); +/// ``` +/// +/// But you can build a record: +/// +/// ``` +/// use typed_builder::TypedBuilder; +/// +/// #[derive(PartialEq, TypedBuilder)] +/// struct Foo { +/// #[builder(default, setter(skip))] +/// y: i8, +/// } +/// +/// let _ = Foo::builder().build(); +/// ``` +/// +/// `skip` without `default` is disallowed: +/// (“error: #[builder(skip)] must be accompanied by default”) +/// +/// ```compile_fail +/// use typed_builder::TypedBuilder; +/// +/// #[derive(PartialEq, TypedBuilder)] +/// struct Foo { +/// #[builder(setter(skip))] +/// y: i8, +/// } +/// ``` +/// +/// `clone` does not work if non-Clone fields have already been set +/// +/// ```compile_fail +/// use typed_builder::TypedBuilder; +/// +/// #[derive(Default)] +/// struct Uncloneable; +/// +/// #[derive(TypedBuilder)] +/// struct Foo { +/// x: Uncloneable, +/// y: i32, +/// } +/// +/// let _ = Foo::builder().x(Uncloneable).clone(); +/// ``` +/// +/// Same, but with generics +/// +/// ```compile_fail +/// use typed_builder::TypedBuilder; +/// +/// #[derive(Default)] +/// struct Uncloneable; +/// +/// #[derive(TypedBuilder)] +/// struct Foo { +/// x: T, +/// y: i32, +/// } +/// +/// let _ = Foo::builder().x(Uncloneable).clone(); +/// ``` +fn _compile_fail_tests() {} diff --git a/proc-macros/src/struct_info.rs b/src/struct_info.rs similarity index 95% rename from proc-macros/src/struct_info.rs rename to src/struct_info.rs index 789bc2fc..44593ea7 100644 --- a/proc-macros/src/struct_info.rs +++ b/src/struct_info.rs @@ -1,9 +1,7 @@ use either::Either::*; use proc_macro2::TokenStream; -use proc_macro_crate::{crate_name, FoundCrate}; use quote::{format_ident, quote}; use regex::Regex; -use std::iter::FromIterator; use syn::parse::Error; use syn::punctuated::Punctuated; @@ -30,6 +28,7 @@ pub struct StructInfo<'a> { pub builder_attr: TypeBuilderAttr, pub builder_name: syn::Ident, + pub conversion_helper_trait_name: syn::Ident, pub core: syn::Ident, } @@ -139,6 +138,7 @@ impl<'a> StructInfo<'a> { .map(|(i, f)| FieldInfo::new(i, f, builder_attr.field_defaults.clone(), &generic_defaults)) .collect::>()?, builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()), + conversion_helper_trait_name: syn::Ident::new(&format!("{}_Optional", builder_name), proc_macro2::Span::call_site()), core: syn::Ident::new(&format!("{}_core", builder_name), proc_macro2::Span::call_site()), builder_attr, generics_without_defaults, @@ -329,6 +329,32 @@ impl<'a> StructInfo<'a> { }) } + // TODO: once the proc-macro crate limitation is lifted, make this an util trait of this + // crate. + pub fn conversion_helper_impl(&self) -> TokenStream { + let trait_name = &self.conversion_helper_trait_name; + quote! { + #[doc(hidden)] + #[allow(dead_code, non_camel_case_types, non_snake_case)] + pub trait #trait_name { + fn into_value Option>(self, default: F) -> T; + } + + impl #trait_name for () { + fn into_value Option>(self, default: F) -> T { + default().unwrap() + } + } + + impl #trait_name for (T,) { + fn into_value Option>(self, _: F) -> T { + self.0 + } + } + } + } + + pub fn field_impl(&self, field: &FieldInfo) -> Result { let StructInfo { ref builder_name, .. } = *self; @@ -642,24 +668,16 @@ impl<'a> StructInfo<'a> { paren_token: None, lifetimes: None, modifier: syn::TraitBoundModifier::None, - path: syn::Path { - leading_colon: None, - segments: Punctuated::from_iter(vec![ - syn::PathSegment { - ident: format_ident!("typed_builder"), - arguments: syn::PathArguments::None, - }, - syn::PathSegment { - ident: format_ident!("BuilderOptional"), - arguments: syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { - colon2_token: None, - lt_token: Default::default(), - args: make_punctuated_single(syn::GenericArgument::Type(field.ty.clone())), - gt_token: Default::default(), - }), - }, - ].into_iter()), - }, + path: syn::PathSegment { + ident: self.conversion_helper_trait_name.clone(), + arguments: syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: make_punctuated_single(syn::GenericArgument::Type(field.ty.clone())), + gt_token: Default::default(), + }), + } + .into(), }; let mut generic_param: syn::TypeParam = field.generic_ident.clone().into(); generic_param.bounds.push(trait_ref.into()); @@ -689,15 +707,7 @@ impl<'a> StructInfo<'a> { let descructuring = self.included_fields().map(|f| f.name); - let found_crate = crate_name("typed-builder").expect("typed-builder is present in `Cargo.toml`"); - let crate_name = match found_crate { - FoundCrate::Itself => quote!(typed_builder), - FoundCrate::Name(name) => { - let ident = syn::Ident::new(&name, proc_macro2::Span::call_site()); - quote!(#ident) - } - }; - + let helper_trait_name = &self.conversion_helper_trait_name; // The default of a field can refer to earlier-defined fields, which we handle by // writing out a bunch of `let` statements first, which can each refer to earlier ones. // This means that field ordering may actually be significant, which isn't ideal. We could @@ -709,9 +719,9 @@ impl<'a> StructInfo<'a> { if field.builder_attr.setter.skip.is_some() { quote!(let #name = #default;) } else if !field.used_default_generic_idents.is_empty() { - quote!(let #name = #crate_name::BuilderOptional::into_value(#name, || None);) + quote!(let #name = #helper_trait_name::into_value(#name, || None);) } else { - quote!(let #name = #crate_name::BuilderOptional::into_value(#name, || Some(#default));) + quote!(let #name = #helper_trait_name::into_value(#name, || Some(#default));) } } else { quote!(let #name = #name.0;) diff --git a/proc-macros/src/util.rs b/src/util.rs similarity index 100% rename from proc-macros/src/util.rs rename to src/util.rs From a708e3cba2d1e1df029bccce7138f22aac0a629c Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 04:29:10 -0800 Subject: [PATCH 07/11] remove pretty print debug feature --- Cargo.toml | 4 ---- src/lib.rs | 7 ++----- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1d15f67f..1cf85e4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,7 @@ proc-macro = true [dependencies] either = "1" -prettyplease = { version = "0.1", optional = true } proc-macro2 = "1" quote = "1" regex = "1" syn = { version = "1", features = ["full", "extra-traits"] } - -[features] -debug = ["dep:prettyplease"] diff --git a/src/lib.rs b/src/lib.rs index 793c1c9f..3bf0be7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,13 +159,10 @@ mod util; #[proc_macro_derive(TypedBuilder, attributes(builder))] pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); - let tokens: proc_macro::TokenStream = match impl_my_derive(&input) { + match impl_my_derive(&input) { Ok(output) => output.into(), Err(error) => error.to_compile_error().into(), - }; - #[cfg(feature = "debug")] - println!("{}", prettyplease::unparse(&syn::parse_file(&format!("{tokens}")).unwrap())); - tokens + } } fn impl_my_derive(ast: &syn::DeriveInput) -> Result { From 6022ce36987f06efd55fdbfd593649dee1e684ec Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 04:30:43 -0800 Subject: [PATCH 08/11] ran cargo fmt --- src/field_info.rs | 14 ++++-- src/struct_info.rs | 108 +++++++++++++++++++++++++++++---------------- 2 files changed, 81 insertions(+), 41 deletions(-) diff --git a/src/field_info.rs b/src/field_info.rs index 4c9b225d..64b464fa 100644 --- a/src/field_info.rs +++ b/src/field_info.rs @@ -5,7 +5,9 @@ use std::collections::HashSet; use syn::parse::Error; use syn::spanned::Spanned; -use crate::util::{empty_type, expr_to_single_string, GenericDefault, ident_to_type, path_to_single_string, strip_raw_ident_prefix}; +use crate::util::{ + empty_type, expr_to_single_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, GenericDefault, +}; #[derive(Debug)] pub struct FieldInfo<'a> { @@ -19,7 +21,12 @@ pub struct FieldInfo<'a> { } impl<'a> FieldInfo<'a> { - pub fn new(ordinal: usize, field: &'a syn::Field, field_defaults: FieldBuilderAttr, generic_defaults: &[GenericDefault]) -> Result { + pub fn new( + ordinal: usize, + field: &'a syn::Field, + field_defaults: FieldBuilderAttr, + generic_defaults: &[GenericDefault], + ) -> Result { if let Some(ref name) = field.ident { let mut field_info = FieldInfo { ordinal, @@ -46,7 +53,7 @@ impl<'a> FieldInfo<'a> { Left(type_param) => field_info.used_default_generic_idents.insert(type_param.ident.clone()), Right(const_param) => field_info.used_default_generic_idents.insert(const_param.ident.clone()), }; - }, + } None => { ty_includes_params_without_defaults = true; break; @@ -69,7 +76,6 @@ impl<'a> FieldInfo<'a> { } Ok(field_info) - } else { Err(Error::new(field.span(), "Nameless field in struct")) } diff --git a/src/struct_info.rs b/src/struct_info.rs index 44593ea7..a29e32ca 100644 --- a/src/struct_info.rs +++ b/src/struct_info.rs @@ -7,8 +7,8 @@ use syn::punctuated::Punctuated; use crate::field_info::{FieldBuilderAttr, FieldInfo}; use crate::util::{ - empty_type_tuple, expr_to_single_string, expr_tuple, GenericDefault, make_punctuated_single, modify_types_generics_hack, - path_to_single_string, strip_raw_ident_prefix, type_tuple, + empty_type_tuple, expr_to_single_string, expr_tuple, make_punctuated_single, modify_types_generics_hack, + path_to_single_string, strip_raw_ident_prefix, type_tuple, GenericDefault, }; #[derive(Debug)] @@ -42,7 +42,8 @@ impl<'a> StructInfo<'a> { let builder_name = strip_raw_ident_prefix(format!("{}Builder", ast.ident)); let mut generics_without_defaults = ast.generics.clone(); - generics_without_defaults.params = generics_without_defaults.params + generics_without_defaults.params = generics_without_defaults + .params .into_iter() .map(|param| match param { syn::GenericParam::Type(type_param) => syn::GenericParam::Type(syn::TypeParam { @@ -66,7 +67,9 @@ impl<'a> StructInfo<'a> { }) .collect(); - let ty_generics_with_defaults: Punctuated<_, syn::token::Comma> = ast.generics.params + let ty_generics_with_defaults: Punctuated<_, syn::token::Comma> = ast + .generics + .params .clone() .into_iter() .map::, _>(|param| match param { @@ -75,55 +78,78 @@ impl<'a> StructInfo<'a> { None => { let ident = type_param.ident; syn::parse(proc_macro::TokenStream::from(quote!(#ident))) - }, + } }, - syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) => syn::parse(proc_macro::TokenStream::from(quote!(#lifetime))), + syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) => { + syn::parse(proc_macro::TokenStream::from(quote!(#lifetime))) + } syn::GenericParam::Const(const_param) => match const_param.default { Some(default) => syn::parse(proc_macro::TokenStream::from(quote!(#default))), None => { let ident = const_param.ident; syn::parse(proc_macro::TokenStream::from(quote!(#ident))) - }, + } }, }) .collect::>()?; let mut no_default_generics = ast.generics.clone(); let mut generic_defaults = Vec::::default(); - no_default_generics.params = no_default_generics.params + no_default_generics.params = no_default_generics + .params .into_iter() .filter_map(|param| match param { syn::GenericParam::Type(type_param) => match type_param.default.clone() { Some(default) => { let ident = &type_param.ident; - let regular_expression = Regex::new(format!(r#"\b{}\b"#, quote!(#ident)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#type_param)))); - generic_defaults.push((Left(type_param), regular_expression, Some(format!("{}", quote!(#default)).trim().to_string()))); + let regular_expression = Regex::new(format!(r#"\b{}\b"#, quote!(#ident)).trim()).expect(&format!( + "unable to replace generic parameter `{}`, not a matchable regex pattern", + format!("{}", quote!(#type_param)) + )); + generic_defaults.push(( + Left(type_param), + regular_expression, + Some(format!("{}", quote!(#default)).trim().to_string()), + )); None - }, + } None => { generic_defaults.push(( Left(type_param.clone()), - Regex::new(format!(r#"\b{}\b"#, quote!(#type_param)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#type_param)))), + Regex::new(format!(r#"\b{}\b"#, quote!(#type_param)).trim()).expect(&format!( + "unable to replace generic parameter `{}`, not a matchable regex pattern", + format!("{}", quote!(#type_param)) + )), None, )); Some(syn::GenericParam::Type(type_param)) - }, + } }, syn::GenericParam::Const(const_param) => match const_param.default.clone() { Some(default) => { let ident = &const_param.ident; - let regular_expression = Regex::new(format!(r#"\b{}\b"#, quote!(#ident)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#const_param)))); - generic_defaults.push((Right(const_param), regular_expression, Some(format!("{}", quote!(#default)).trim().to_string()))); + let regular_expression = Regex::new(format!(r#"\b{}\b"#, quote!(#ident)).trim()).expect(&format!( + "unable to replace generic parameter `{}`, not a matchable regex pattern", + format!("{}", quote!(#const_param)) + )); + generic_defaults.push(( + Right(const_param), + regular_expression, + Some(format!("{}", quote!(#default)).trim().to_string()), + )); None - }, + } None => { generic_defaults.push(( Right(const_param.clone()), - Regex::new(format!(r#"\b{}\b"#, quote!(#const_param)).trim()).expect(&format!("unable to replace generic parameter `{}`, not a matchable regex pattern", format!("{}", quote!(#const_param)))), + Regex::new(format!(r#"\b{}\b"#, quote!(#const_param)).trim()).expect(&format!( + "unable to replace generic parameter `{}`, not a matchable regex pattern", + format!("{}", quote!(#const_param)) + )), None, )); Some(syn::GenericParam::Const(const_param)) - }, + } }, param => Some(param), }) @@ -154,19 +180,25 @@ impl<'a> StructInfo<'a> { generics } - fn modify_generics_alter_if_used_default_generic(&self, mut mutator: F, field: &FieldInfo) -> syn::Generics { + fn modify_generics_alter_if_used_default_generic( + &self, + mut mutator: F, + field: &FieldInfo, + ) -> syn::Generics { let mut generics = self.generics.clone(); - generics.params - .iter_mut() - .for_each(|param| match param { - syn::GenericParam::Type(ref mut type_param) => if type_param.default.is_some() && field.used_default_generic_idents.contains(&type_param.ident) { + generics.params.iter_mut().for_each(|param| match param { + syn::GenericParam::Type(ref mut type_param) => { + if type_param.default.is_some() && field.used_default_generic_idents.contains(&type_param.ident) { type_param.ident = format_ident_target_generic_default(&type_param.ident); - }, - syn::GenericParam::Const(ref mut const_param) => if const_param.default.is_some() && field.used_default_generic_idents.contains(&const_param.ident) { + } + } + syn::GenericParam::Const(ref mut const_param) => { + if const_param.default.is_some() && field.used_default_generic_idents.contains(&const_param.ident) { const_param.ident = format_ident_target_generic_default(&const_param.ident); - }, - _ => {}, - }); + } + } + _ => {} + }); mutator(&mut generics); generics } @@ -178,7 +210,8 @@ impl<'a> StructInfo<'a> { } fn ty_generics_with_defaults_except_field(&self, field: &FieldInfo) -> Result, Error> { - self.generics.params + self.generics + .params .clone() .into_iter() .map::, _>(|param| match param { @@ -186,22 +219,24 @@ impl<'a> StructInfo<'a> { true => { let ident = format_ident_target_generic_default(&type_param.ident); syn::parse(proc_macro::TokenStream::from(quote!(#ident))) - }, + } false => { let ident = &type_param.ident; syn::parse(proc_macro::TokenStream::from(quote!(#ident))) - }, + } }, - syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) => syn::parse(proc_macro::TokenStream::from(quote!(#lifetime))), + syn::GenericParam::Lifetime(syn::LifetimeDef { lifetime, .. }) => { + syn::parse(proc_macro::TokenStream::from(quote!(#lifetime))) + } syn::GenericParam::Const(const_param) => match field.used_default_generic_idents.contains(&const_param.ident) { true => { let ident = format_ident_target_generic_default(&const_param.ident); syn::parse(proc_macro::TokenStream::from(quote!(#ident))) - }, + } false => { let ident = &const_param.ident; syn::parse(proc_macro::TokenStream::from(quote!(#ident))) - }, + } }, }) .collect::>() @@ -354,7 +389,6 @@ impl<'a> StructInfo<'a> { } } - pub fn field_impl(&self, field: &FieldInfo) -> Result { let StructInfo { ref builder_name, .. } = *self; @@ -471,13 +505,13 @@ impl<'a> StructInfo<'a> { type_param.eq_token = None; type_param.default = None; generic_params.push(quote!(#type_param)); - }, + } Right(const_param) => { let mut const_param = const_param.clone(); const_param.eq_token = None; const_param.default = None; generic_params.push(quote!(#const_param)); - }, + } } } } From baaf5477dcb1b5e082c6ef051180f99d7724c0b1 Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 04:46:50 -0800 Subject: [PATCH 09/11] fix bug where field types which depend on both generic params with and without defaults were not properly suffixing the generic types currently existing on the builder --- src/field_info.rs | 1 - tests/tests.rs | 11 +++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/field_info.rs b/src/field_info.rs index 64b464fa..dcafbc7f 100644 --- a/src/field_info.rs +++ b/src/field_info.rs @@ -56,7 +56,6 @@ impl<'a> FieldInfo<'a> { } None => { ty_includes_params_without_defaults = true; - break; } } } diff --git a/tests/tests.rs b/tests/tests.rs index 3402f71d..f84435f4 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -600,4 +600,15 @@ fn test_struct_generic_defaults() { } assert_eq!(Foo::builder().build().x, 12); + + #[allow(dead_code)] + #[derive(TypedBuilder)] + struct Bar { + t: T, + #[builder(default = 12)] + u: U, + v: (T, U, V), + } + + assert_eq!(Bar::builder().t("test").v(("t", 0, 3.14f64)).build().v.0, "t"); } From fb96cc6f1e49ab430a1af15b7be64745a9d5a33c Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 04:47:46 -0800 Subject: [PATCH 10/11] add complex default/no-default generic dependent field type test to examples --- examples/default_generics.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/default_generics.rs b/examples/default_generics.rs index d4fdb1cc..34574941 100644 --- a/examples/default_generics.rs +++ b/examples/default_generics.rs @@ -15,6 +15,15 @@ struct Foo { x: T, } +#[allow(dead_code)] +#[derive(TypedBuilder)] +struct Bar { + t: T, + #[builder(default = 12)] + u: U, + v: (T, U, V), +} + fn main() { let props = Props::builder().label("label").on_input(|x: usize| x).build(); assert_eq!(props.class, None); @@ -22,4 +31,6 @@ fn main() { assert_eq!((props.on_input.unwrap())(123), 123); assert_eq!(Foo::builder().build().x, 12); + + assert_eq!(Bar::builder().t("test").v(("t", 0, 3.14f64)).build().v.0, "t"); } From 7ac7198f8480e2d3dfa1966f7c320e75403030af Mon Sep 17 00:00:00 2001 From: treysidechain Date: Wed, 7 Dec 2022 05:57:56 -0800 Subject: [PATCH 11/11] restore necessity for the #[builder(default)] field attribute to be applied in order for a field to have a default value set --- src/field_info.rs | 18 +++++++----------- tests/tests.rs | 6 +++--- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/field_info.rs b/src/field_info.rs index dcafbc7f..adc8afd1 100644 --- a/src/field_info.rs +++ b/src/field_info.rs @@ -61,17 +61,13 @@ impl<'a> FieldInfo<'a> { } } if !ty_includes_params_without_defaults && ty_includes_params_with_defaults { - use std::str::FromStr; - let expr_str = format!("(<{ty_str} as Default>::default(),)"); - let ty_str = format!("({ty_str},)"); - field_info.default_ty = Some(( - syn::parse(TokenStream::from_str(&ty_str)?.into())?, - if let Some(default_expr) = field_info.builder_attr.default.clone() { - syn::parse(quote! { (#default_expr,) }.into())? - } else { - syn::parse(TokenStream::from_str(&expr_str)?.into())? - }, - )); + if let Some(default_expr) = field_info.builder_attr.default.as_ref() { + use std::str::FromStr; + field_info.default_ty = Some(( + syn::parse(TokenStream::from_str(&format!("({ty_str},)"))?.into())?, + syn::parse(quote! { (#default_expr,) }.into())?, + )); + } } Ok(field_info) diff --git a/tests/tests.rs b/tests/tests.rs index f84435f4..45ee5e02 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -347,13 +347,13 @@ fn test_builder_type_with_default_on_generic_type() { m: M, } - impl<'a, X, Y: Default, M, X_, Y_, M_> FooBuilder<'a, (X_, Y_, (usize,), M_), X, Y, usize, M> { + impl<'a, X, Y: Default, M, X_, Y_, M_> FooBuilder<'a, (X_, Y_, (), M_), X, Y, usize, M> { fn z_default(self) -> FooBuilder<'a, (X_, Y_, (usize,), M_), X, Y, usize, M> { self.z(usize::default()) } } - impl<'a, X, Y: Default, Z: Default, X_, Y_, Z_> FooBuilder<'a, (X_, Y_, Z_, ((),)), X, Y, Z, ()> { + impl<'a, X, Y: Default, Z: Default, X_, Y_, Z_> FooBuilder<'a, (X_, Y_, Z_, ()), X, Y, Z, ()> { fn m_default(self) -> FooBuilder<'a, (X_, Y_, Z_, ((),)), X, Y, Z, ()> { self.m(()) } @@ -582,7 +582,7 @@ fn test_struct_generic_defaults() { #[builder(default, setter(into))] pub class: Option<&'a str>, pub label: &'a str, - #[builder(setter(strip_option))] + #[builder(default = Some(Box::new(|x: usize| x + 1)), setter(strip_option))] pub on_input: Option, }