From 2c64641c5b3987216688c96a448c7c2a68714de9 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 15:07:05 +1000 Subject: [PATCH 01/21] replace_generics for normal value setters --- builder-pattern-macro/src/attributes.rs | 28 ++++++++- .../src/builder/builder_decl.rs | 2 +- .../src/builder/builder_functions.rs | 53 +++++++++++++---- .../src/builder/builder_impl.rs | 2 +- builder-pattern-macro/src/field.rs | 21 ++++++- builder-pattern-macro/src/lib.rs | 3 +- builder-pattern-macro/src/struct_impl.rs | 2 +- builder-pattern-macro/src/struct_input.rs | 57 ++++++++++++++++++- builder-pattern/examples/default-generics.rs | 16 ++++++ 9 files changed, 163 insertions(+), 21 deletions(-) create mode 100644 builder-pattern/examples/default-generics.rs diff --git a/builder-pattern-macro/src/attributes.rs b/builder-pattern-macro/src/attributes.rs index de8002c..fe10d7b 100644 --- a/builder-pattern-macro/src/attributes.rs +++ b/builder-pattern-macro/src/attributes.rs @@ -1,5 +1,5 @@ use bitflags::bitflags; -use syn::{Attribute, Expr, Meta, NestedMeta}; +use syn::{Attribute, Expr, Ident, Meta, NestedMeta}; bitflags! { pub struct Setters: u32 { @@ -23,6 +23,7 @@ pub struct FieldAttributes { pub documents: Vec, pub setters: Setters, pub vis: FieldVisibility, + pub replace_generics: Vec, } impl Default for FieldAttributes { @@ -34,6 +35,7 @@ impl Default for FieldAttributes { documents: vec![], setters: Setters::VALUE, vis: FieldVisibility::Default, + replace_generics: vec![], } } } @@ -75,6 +77,8 @@ impl From> for FieldAttributes { attributes.documents = get_documents(&attrs); } else if attr.path.is_ident("setter") { parse_setters(attr, &mut attributes) + } else if attr.path.is_ident("replace_generics") { + parse_replace_generics(attr, &mut attributes) } }); match attributes.validate() { @@ -129,6 +133,28 @@ fn parse_setters(attr: &Attribute, attributes: &mut FieldAttributes) { attributes.setters = setters; } +fn parse_replace_generics(attr: &Attribute, attributes: &mut FieldAttributes) { + let meta = attr.parse_meta().unwrap(); + let mut replace_generics = vec![]; + if let Meta::List(l) = meta { + let it = l.nested.iter(); + it.for_each(|m| { + if let NestedMeta::Meta(Meta::Path(p)) = m { + if let Some(ident) = p.get_ident() { + replace_generics.push(ident.clone()); + } else { + unimplemented!("Invalid replace_generics, write a type parameter.") + } + } else { + unimplemented!("Invalid setter.") + } + }); + } else { + unimplemented!("Invalid setter.") + } + attributes.replace_generics = replace_generics; +} + pub fn get_documents(attrs: &[Attribute]) -> Vec { let mut documents: Vec = vec![]; diff --git a/builder-pattern-macro/src/builder/builder_decl.rs b/builder-pattern-macro/src/builder/builder_decl.rs index b256580..aeb2cbb 100644 --- a/builder-pattern-macro/src/builder/builder_decl.rs +++ b/builder-pattern-macro/src/builder/builder_decl.rs @@ -24,7 +24,7 @@ impl<'a> ToTokens for BuilderDecl<'a> { let impl_tokens = self.input.tokenize_impl(); let all_generics = self.input.all_generics().collect::>(); - let ty_tokens = self.input.tokenize_types(); + let ty_tokens = self.input.tokenize_types(&[]); let fn_lifetime = self.input.fn_lifetime(); let builder_fields = self.input.builder_fields(&fn_lifetime); diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 66ad37a..3e60ef5 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -5,7 +5,7 @@ use crate::{ }; use core::str::FromStr; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; use quote::ToTokens; use syn::{parse_quote, spanned::Spanned, Attribute}; @@ -52,6 +52,25 @@ impl<'a> ToTokens for BuilderFunctions<'a> { } } +fn replace_type_params_in(stream: TokenStream, replacements: &[Ident]) -> TokenStream { + stream + .into_iter() + .map(|tt| match tt { + TokenTree::Group(g) => { + let delim = g.delimiter(); + let stream = replace_type_params_in(g.stream(), replacements); + TokenTree::Group(Group::new(delim, stream)) + } + TokenTree::Ident(ident) if replacements.contains(&ident) => { + let ident_ = ident.to_string() + "_"; + let ident = Ident::new(&ident_, ident.span()); + TokenTree::Ident(ident) + } + x => x, + }) + .collect() +} + impl<'a> BuilderFunctions<'a> { pub fn new(input: &'a StructInput) -> Self { Self { input } @@ -101,21 +120,31 @@ impl<'a> BuilderFunctions<'a> { index: usize, builder_fields: &mut Vec, ) { - let (ident, ty, vis) = (&f.ident, &f.ty, &f.vis); + let (ident, orig_ty, vis) = (&f.ident, &f.ty, &f.vis); let builder_name = self.input.builder_name(); let where_clause = &self.input.generics.where_clause; let lifetimes = self.input.lifetimes(); let fn_lifetime = self.input.fn_lifetime(); let impl_tokens = self.input.tokenize_impl(); - let ty_tokens = self.input.tokenize_types(); - let (other_generics, before_generics, after_generics) = self.get_generics(f, index); + let ty_tokens = self.input.tokenize_types(&[]); + let ty_tokens_ = self.input.tokenize_types(&f.attrs.replace_generics); + let fn_generics = f.tokenize_replacement_params(); + let fn_where_clause = self.input.setter_where_clause(&f.attrs.replace_generics); + let (other_generics, before_generics, mut after_generics) = self.get_generics(f, index); + let replaced_ty = replace_type_params_in(quote! { #orig_ty }, &f.attrs.replace_generics); + after_generics + .iter_mut() + .for_each(|ty_tokens: &mut TokenStream| { + let tokens = std::mem::take(ty_tokens); + *ty_tokens = replace_type_params_in(tokens, &f.attrs.replace_generics); + }); let (arg_type_gen, arg_type) = if f.attrs.use_into { ( - Some(quote! {>}), + quote! {>}, TokenStream::from_str("IntoType").unwrap(), ) } else { - (None, quote! {#ty}) + (fn_generics, quote! { #replaced_ty }) }; let documents = Self::documents(f, Setters::VALUE); @@ -131,7 +160,7 @@ impl<'a> BuilderFunctions<'a> { Result<#builder_name < #fn_lifetime, #(#lifetimes,)* - #ty_tokens + #ty_tokens_ #(#after_generics,)* AsyncFieldMarker, ValidatorOption @@ -161,7 +190,7 @@ impl<'a> BuilderFunctions<'a> { #builder_name < #fn_lifetime, #(#lifetimes,)* - #ty_tokens + #ty_tokens_ #(#after_generics,)* AsyncFieldMarker, ValidatorOption @@ -195,7 +224,9 @@ impl<'a> BuilderFunctions<'a> { #where_clause { #(#documents)* - #vis fn #ident #arg_type_gen(self, value: #arg_type) -> #ret_type { + #vis fn #ident #arg_type_gen(self, value: #arg_type) -> #ret_type + #fn_where_clause + { #ret_expr } } @@ -216,7 +247,7 @@ impl<'a> BuilderFunctions<'a> { let lifetimes = self.input.lifetimes(); let fn_lifetime = self.input.fn_lifetime(); let impl_tokens = self.input.tokenize_impl(); - let ty_tokens = self.input.tokenize_types(); + let ty_tokens = self.input.tokenize_types(&[]); let (other_generics, before_generics, after_generics) = self.get_generics(f, index); let arg_type_gen = if f.attrs.use_into { quote! {, ValType: #fn_lifetime + ::core::ops::Fn() -> IntoType>} @@ -306,7 +337,7 @@ impl<'a> BuilderFunctions<'a> { let lifetimes = self.input.lifetimes(); let fn_lifetime = self.input.fn_lifetime(); let impl_tokens = self.input.tokenize_impl(); - let ty_tokens = self.input.tokenize_types(); + let ty_tokens = self.input.tokenize_types(&[]); let (other_generics, before_generics, after_generics) = self.get_generics(f, index); let arg_type_gen = if f.attrs.use_into { quote! {< diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 10706a3..ae91ac2 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -77,7 +77,7 @@ impl<'a> BuilderImpl<'a> { let impl_tokens = self.input.tokenize_impl(); let optional_generics = self.optional_generics().collect::>(); let satisfied_generics = self.satified_generics().collect::>(); - let ty_tokens = self.input.tokenize_types(); + let ty_tokens = self.input.tokenize_types(&[]); let mut struct_init_args = vec![]; let mut validated_init_fields = vec![]; diff --git a/builder-pattern-macro/src/field.rs b/builder-pattern-macro/src/field.rs index 2578c5a..fcff24b 100644 --- a/builder-pattern-macro/src/field.rs +++ b/builder-pattern-macro/src/field.rs @@ -1,9 +1,9 @@ use super::attributes::FieldAttributes; use core::cmp::Ordering; -use proc_macro2::Ident; -use quote::ToTokens; -use syn::{Attribute, Type, Visibility}; +use proc_macro2::{Ident, TokenStream}; +use quote::{ToTokens, TokenStreamExt}; +use syn::{Attribute, Token, Type, Visibility}; pub struct Field { pub vis: Visibility, @@ -30,6 +30,21 @@ impl Field { ty_token.to_string() } } + + pub fn tokenize_replacement_params(&self) -> TokenStream { + let mut stream = TokenStream::new(); + if self.attrs.replace_generics.is_empty() { + return stream; + } + let underscored = self.attrs.replace_generics.iter().map(|ident| { + let string = ident.to_string() + "_"; + Ident::new(&string, ident.span()) + }); + ::default().to_tokens(&mut stream); + stream.append_terminated(underscored, ::default()); + ]>::default().to_tokens(&mut stream); + stream + } } impl Ord for Field { diff --git a/builder-pattern-macro/src/lib.rs b/builder-pattern-macro/src/lib.rs index 18c59e6..aa4f38b 100644 --- a/builder-pattern-macro/src/lib.rs +++ b/builder-pattern-macro/src/lib.rs @@ -30,7 +30,8 @@ extern crate proc_macro2; into, public, setter, - validator + validator, + replace_generics, ) )] pub fn derive_builder(input: TokenStream) -> TokenStream { diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 9eaa6de..9fbeef7 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -21,7 +21,7 @@ impl<'a> ToTokens for StructImpl<'a> { let lifetimes = self.input.lifetimes(); let impl_tokens = self.input.tokenize_impl(); let empty_generics = self.empty_generics(); - let ty_tokens = self.input.tokenize_types(); + let ty_tokens = self.input.tokenize_types(&[]); let fn_lifetime = self.input.fn_lifetime(); diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index 72f8a32..fffbff0 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -13,6 +13,7 @@ use syn::{ AttrStyle, Attribute, Data, DeriveInput, Fields, GenericParam, Generics, Lifetime, Token, VisPublic, Visibility, }; +use syn::{Path, PredicateType, Type, TypePath, WhereClause, WherePredicate}; pub struct StructInput { pub vis: Visibility, @@ -151,7 +152,7 @@ impl StructInput { /// Tokenize type parameters. /// It skips lifetimes and has no outer brackets. - pub fn tokenize_types(&self) -> TokenStream { + pub fn tokenize_types(&self, replace_generics: &[Ident]) -> TokenStream { let generics = &self.generics; let mut tokens = TokenStream::new(); @@ -177,7 +178,13 @@ impl StructInput { GenericParam::Lifetime(_) => unreachable!(), GenericParam::Type(param) => { // Leave off the type parameter defaults - param.ident.to_tokens(&mut tokens); + if replace_generics.contains(¶m.ident) { + let str = param.ident.to_string() + "_"; + let ident = Ident::new(&str, param.ident.span()); + ident.to_tokens(&mut tokens); + } else { + param.ident.to_tokens(&mut tokens); + } } GenericParam::Const(param) => { // Leave off the const parameter defaults @@ -190,6 +197,52 @@ impl StructInput { tokens } + pub fn setter_where_clause(&self, replace_generics: &[Ident]) -> TokenStream { + let mut stream = TokenStream::new(); + if replace_generics.is_empty() { + return stream; + } + let clauses = self + .generics + .where_clause + .iter() + .flat_map(|where_clause: &WhereClause| { + where_clause + .predicates + .iter() + .filter_map(|x: &WherePredicate| match x { + WherePredicate::Type(PredicateType { + bounded_ty: Type::Path(TypePath { qself: None, path }), + bounds, + lifetimes, + colon_token, + .. + }) => { + if let Some(ident) = path.get_ident() { + let ident_ = ident.to_string() + "_"; + let replacement_ident = Ident::new(&ident_, ident.span()); + let pred = WherePredicate::Type(PredicateType { + bounded_ty: Type::Path(TypePath { + qself: None, + path: Path::from(replacement_ident), + }), + bounds: bounds.clone(), + lifetimes: lifetimes.clone(), + colon_token: colon_token.clone(), + }); + Some(quote! { #pred }) + } else { + None + } + } + _ => None, + }) + }); + stream.extend(quote! { where }); + stream.append_terminated(clauses, quote! { , }); + stream + } + /// Tokenize parameters for `impl` blocks. /// It doesn't contain outer brackets, but lifetimes and trait bounds. pub fn tokenize_impl(&self) -> TokenStream { diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs new file mode 100644 index 0000000..1b4985a --- /dev/null +++ b/builder-pattern/examples/default-generics.rs @@ -0,0 +1,16 @@ +use builder_pattern::Builder; + +#[allow(unused)] +#[derive(Builder)] +struct Op { + #[replace_generics(T)] + #[default(None)] + optional_field: Option, +} + +fn main() { + // Should be inferred as Op<&'static str>, i.e. the macro should notice the defaulted type param. + let _ = Op::new().build(); + // Should be inferred as Op + let _ = Op::new().optional_field(Some(5i32)).build(); +} From f5a6281b051663152a03ebd38fd1f0fec6b3284b Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 16:06:19 +1000 Subject: [PATCH 02/21] replace_generics working --- builder-pattern-macro/src/attributes.rs | 10 ++++ .../src/builder/builder_decl.rs | 4 +- .../src/builder/builder_functions.rs | 44 +++++++++------ .../src/builder/builder_impl.rs | 4 +- builder-pattern-macro/src/struct_impl.rs | 55 +++++++++++++++++-- builder-pattern-macro/src/struct_input.rs | 36 ++++++++++-- builder-pattern/examples/default-generics.rs | 8 ++- 7 files changed, 125 insertions(+), 36 deletions(-) diff --git a/builder-pattern-macro/src/attributes.rs b/builder-pattern-macro/src/attributes.rs index fe10d7b..5dddf93 100644 --- a/builder-pattern-macro/src/attributes.rs +++ b/builder-pattern-macro/src/attributes.rs @@ -1,4 +1,5 @@ use bitflags::bitflags; +use proc_macro2::TokenTree; use syn::{Attribute, Expr, Ident, Meta, NestedMeta}; bitflags! { @@ -26,6 +27,15 @@ pub struct FieldAttributes { pub replace_generics: Vec, } +pub fn ident_add_underscore(ident: &Ident) -> Ident { + let ident_ = ident.to_string() + "_"; + Ident::new(&ident_, ident.span()) +} + +pub fn ident_add_underscore_tree(ident: &Ident) -> TokenTree { + TokenTree::Ident(ident_add_underscore(ident)) +} + impl Default for FieldAttributes { fn default() -> Self { FieldAttributes { diff --git a/builder-pattern-macro/src/builder/builder_decl.rs b/builder-pattern-macro/src/builder/builder_decl.rs index aeb2cbb..92e904a 100644 --- a/builder-pattern-macro/src/builder/builder_decl.rs +++ b/builder-pattern-macro/src/builder/builder_decl.rs @@ -22,9 +22,9 @@ impl<'a> ToTokens for BuilderDecl<'a> { let builder_name = self.input.builder_name(); let where_clause = &self.input.generics.where_clause; - let impl_tokens = self.input.tokenize_impl(); + let impl_tokens = self.input.tokenize_impl(&[]); let all_generics = self.input.all_generics().collect::>(); - let ty_tokens = self.input.tokenize_types(&[]); + let ty_tokens = self.input.tokenize_types(&[], false); let fn_lifetime = self.input.fn_lifetime(); let builder_fields = self.input.builder_fields(&fn_lifetime); diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 3e60ef5..ec428f6 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -1,11 +1,11 @@ use crate::{ - attributes::{FieldVisibility, Setters}, + attributes::{ident_add_underscore_tree, FieldVisibility, Setters}, field::Field, struct_input::StructInput, }; use core::str::FromStr; -use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; +use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree}; use quote::ToTokens; use syn::{parse_quote, spanned::Spanned, Attribute}; @@ -52,20 +52,20 @@ impl<'a> ToTokens for BuilderFunctions<'a> { } } -fn replace_type_params_in(stream: TokenStream, replacements: &[Ident]) -> TokenStream { +pub fn replace_type_params_in( + stream: TokenStream, + replacements: &[Ident], + with: &impl Fn(&Ident) -> TokenTree, +) -> TokenStream { stream .into_iter() .map(|tt| match tt { TokenTree::Group(g) => { let delim = g.delimiter(); - let stream = replace_type_params_in(g.stream(), replacements); + let stream = replace_type_params_in(g.stream(), replacements, with); TokenTree::Group(Group::new(delim, stream)) } - TokenTree::Ident(ident) if replacements.contains(&ident) => { - let ident_ = ident.to_string() + "_"; - let ident = Ident::new(&ident_, ident.span()); - TokenTree::Ident(ident) - } + TokenTree::Ident(ident) if replacements.contains(&ident) => with(&ident), x => x, }) .collect() @@ -125,18 +125,26 @@ impl<'a> BuilderFunctions<'a> { let where_clause = &self.input.generics.where_clause; let lifetimes = self.input.lifetimes(); let fn_lifetime = self.input.fn_lifetime(); - let impl_tokens = self.input.tokenize_impl(); - let ty_tokens = self.input.tokenize_types(&[]); - let ty_tokens_ = self.input.tokenize_types(&f.attrs.replace_generics); + let impl_tokens = self.input.tokenize_impl(&[]); + let ty_tokens = self.input.tokenize_types(&[], false); + let ty_tokens_ = self.input.tokenize_types(&f.attrs.replace_generics, false); let fn_generics = f.tokenize_replacement_params(); let fn_where_clause = self.input.setter_where_clause(&f.attrs.replace_generics); let (other_generics, before_generics, mut after_generics) = self.get_generics(f, index); - let replaced_ty = replace_type_params_in(quote! { #orig_ty }, &f.attrs.replace_generics); + let replaced_ty = replace_type_params_in( + quote! { #orig_ty }, + &f.attrs.replace_generics, + &ident_add_underscore_tree, + ); after_generics .iter_mut() .for_each(|ty_tokens: &mut TokenStream| { let tokens = std::mem::take(ty_tokens); - *ty_tokens = replace_type_params_in(tokens, &f.attrs.replace_generics); + *ty_tokens = replace_type_params_in( + tokens, + &f.attrs.replace_generics, + &ident_add_underscore_tree, + ); }); let (arg_type_gen, arg_type) = if f.attrs.use_into { ( @@ -246,8 +254,8 @@ impl<'a> BuilderFunctions<'a> { let where_clause = &self.input.generics.where_clause; let lifetimes = self.input.lifetimes(); let fn_lifetime = self.input.fn_lifetime(); - let impl_tokens = self.input.tokenize_impl(); - let ty_tokens = self.input.tokenize_types(&[]); + let impl_tokens = self.input.tokenize_impl(&[]); + let ty_tokens = self.input.tokenize_types(&[], false); let (other_generics, before_generics, after_generics) = self.get_generics(f, index); let arg_type_gen = if f.attrs.use_into { quote! {, ValType: #fn_lifetime + ::core::ops::Fn() -> IntoType>} @@ -336,8 +344,8 @@ impl<'a> BuilderFunctions<'a> { let where_clause = &self.input.generics.where_clause; let lifetimes = self.input.lifetimes(); let fn_lifetime = self.input.fn_lifetime(); - let impl_tokens = self.input.tokenize_impl(); - let ty_tokens = self.input.tokenize_types(&[]); + let impl_tokens = self.input.tokenize_impl(&[]); + let ty_tokens = self.input.tokenize_types(&[], false); let (other_generics, before_generics, after_generics) = self.get_generics(f, index); let arg_type_gen = if f.attrs.use_into { quote! {< diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index ae91ac2..e5b63f7 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -74,10 +74,10 @@ impl<'a> BuilderImpl<'a> { let fn_lifetime = self.input.fn_lifetime(); - let impl_tokens = self.input.tokenize_impl(); + let impl_tokens = self.input.tokenize_impl(&[]); let optional_generics = self.optional_generics().collect::>(); let satisfied_generics = self.satified_generics().collect::>(); - let ty_tokens = self.input.tokenize_types(&[]); + let ty_tokens = self.input.tokenize_types(&[], false); let mut struct_init_args = vec![]; let mut validated_init_fields = vec![]; diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 9fbeef7..33233df 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -1,7 +1,10 @@ -use crate::{attributes::Setters, struct_input::StructInput}; +use crate::{ + attributes::Setters, builder::builder_functions::replace_type_params_in, + struct_input::StructInput, +}; use core::str::FromStr; -use proc_macro2::TokenStream; +use proc_macro2::{Group, Ident, TokenStream, TokenTree}; use quote::ToTokens; use syn::{parse_quote, spanned::Spanned, Attribute}; @@ -15,13 +18,44 @@ impl<'a> ToTokens for StructImpl<'a> { fn to_tokens(&self, tokens: &mut TokenStream) { let ident = &self.input.ident; let vis = &self.input.vis; - let where_clause = &self.input.generics.where_clause; let builder_name = self.input.builder_name(); let lifetimes = self.input.lifetimes(); - let impl_tokens = self.input.tokenize_impl(); let empty_generics = self.empty_generics(); - let ty_tokens = self.input.tokenize_types(&[]); + let defaulted_generics = self.defaulted_generics(); + let with_param_default = |ident: &Ident| { + self.input + .generics + .type_params() + .find_map(|x| { + if x.ident == *ident { + let default = x.default.as_ref().unwrap(); + let stream = quote! { #default }; + Some(TokenTree::Group(Group::new( + proc_macro2::Delimiter::None, + stream, + ))) + } else { + None + } + }) + .expect("hmmmm") + }; + + let impl_tokens = self.input.tokenize_impl(&defaulted_generics); + + let where_clause = &self.input.generics.where_clause; + let where_tokens = replace_type_params_in( + quote! { #where_clause }, + &defaulted_generics, + &with_param_default, + ); + + let ty_tokens = replace_type_params_in( + self.input.tokenize_types(&[], false), + &defaulted_generics, + &with_param_default, + ); let fn_lifetime = self.input.fn_lifetime(); @@ -29,7 +63,7 @@ impl<'a> ToTokens for StructImpl<'a> { let docs = self.documents(); tokens.extend(quote! { - impl <#impl_tokens> #ident <#(#lifetimes,)* #ty_tokens> #where_clause { + impl <#impl_tokens> #ident <#(#lifetimes,)* #ty_tokens> #where_tokens { #(#docs)* #[allow(clippy::new_ret_no_self)] #vis fn new<#fn_lifetime>() -> #builder_name< @@ -140,4 +174,13 @@ impl<'a> StructImpl<'a> { docs } + + fn defaulted_generics(&self) -> Vec { + self.input + .generics + .type_params() + .filter(|x| x.default.is_some()) + .map(|x| x.ident.clone()) + .collect() + } } diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index fffbff0..c293988 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -1,4 +1,4 @@ -use crate::attributes::{FieldAttributes, FieldVisibility}; +use crate::attributes::{ident_add_underscore, FieldAttributes, FieldVisibility}; use crate::builder::{ builder_decl::BuilderDecl, builder_functions::BuilderFunctions, builder_impl::BuilderImpl, }; @@ -152,13 +152,22 @@ impl StructInput { /// Tokenize type parameters. /// It skips lifetimes and has no outer brackets. - pub fn tokenize_types(&self, replace_generics: &[Ident]) -> TokenStream { + pub fn tokenize_types(&self, replace_generics: &[Ident], omit_replaced: bool) -> TokenStream { let generics = &self.generics; let mut tokens = TokenStream::new(); if generics.params.is_empty() { return tokens; } + if omit_replaced + && generics.params.iter().all(|x| match x { + GenericParam::Type(param) => replace_generics.contains(¶m.ident), + GenericParam::Const(_) => false, + _ => true, + }) + { + return tokens; + } let mut trailing_or_empty = true; for param in generics.params.pairs() { @@ -179,9 +188,10 @@ impl StructInput { GenericParam::Type(param) => { // Leave off the type parameter defaults if replace_generics.contains(¶m.ident) { - let str = param.ident.to_string() + "_"; - let ident = Ident::new(&str, param.ident.span()); - ident.to_tokens(&mut tokens); + if omit_replaced { + continue; + } + ident_add_underscore(¶m.ident).to_tokens(&mut tokens); } else { param.ident.to_tokens(&mut tokens); } @@ -245,10 +255,21 @@ impl StructInput { /// Tokenize parameters for `impl` blocks. /// It doesn't contain outer brackets, but lifetimes and trait bounds. - pub fn tokenize_impl(&self) -> TokenStream { + pub fn tokenize_impl(&self, filter_out: &[Ident]) -> TokenStream { let mut tokens = TokenStream::new(); let generics = &self.generics; + if generics.params.is_empty() { + return tokens; + } + if generics.params.iter().all(|x| match x { + GenericParam::Type(param) => filter_out.contains(¶m.ident), + GenericParam::Const(_) => false, + _ => true, + }) { + return tokens; + } + let mut trailing_or_empty = true; for param in generics.params.pairs() { if let GenericParam::Lifetime(_) = **param.value() { @@ -267,6 +288,9 @@ impl StructInput { match *param.value() { GenericParam::Lifetime(_) => unreachable!(), GenericParam::Type(param) => { + if filter_out.contains(¶m.ident) { + continue; + } // Leave off the type parameter defaults tokens.append_all(param.attrs.iter().filter(|attr| match attr.style { AttrStyle::Outer => true, diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs index 1b4985a..65cc00b 100644 --- a/builder-pattern/examples/default-generics.rs +++ b/builder-pattern/examples/default-generics.rs @@ -9,8 +9,12 @@ struct Op { } fn main() { + use std::any::{Any, TypeId}; // Should be inferred as Op<&'static str>, i.e. the macro should notice the defaulted type param. - let _ = Op::new().build(); + let a = Op::new().build(); + assert_eq!(Any::type_id(&a), TypeId::of::>()); + // Should be inferred as Op - let _ = Op::new().optional_field(Some(5i32)).build(); + let a = Op::new().optional_field(Some(5i32)).build(); + assert_eq!(Any::type_id(&a), TypeId::of::>()); } From 854cd3d96838177f1df25b6fa65b0e9d46a7846b Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 16:07:47 +1000 Subject: [PATCH 03/21] rename `replace_generics` to `infer` --- builder-pattern-macro/src/attributes.rs | 14 +++++++------- .../src/builder/builder_functions.rs | 8 ++++---- builder-pattern-macro/src/field.rs | 4 ++-- builder-pattern-macro/src/lib.rs | 2 +- builder-pattern-macro/src/struct_input.rs | 10 +++++----- builder-pattern/examples/default-generics.rs | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/builder-pattern-macro/src/attributes.rs b/builder-pattern-macro/src/attributes.rs index 5dddf93..00eb878 100644 --- a/builder-pattern-macro/src/attributes.rs +++ b/builder-pattern-macro/src/attributes.rs @@ -24,7 +24,7 @@ pub struct FieldAttributes { pub documents: Vec, pub setters: Setters, pub vis: FieldVisibility, - pub replace_generics: Vec, + pub infer: Vec, } pub fn ident_add_underscore(ident: &Ident) -> Ident { @@ -45,7 +45,7 @@ impl Default for FieldAttributes { documents: vec![], setters: Setters::VALUE, vis: FieldVisibility::Default, - replace_generics: vec![], + infer: vec![], } } } @@ -87,7 +87,7 @@ impl From> for FieldAttributes { attributes.documents = get_documents(&attrs); } else if attr.path.is_ident("setter") { parse_setters(attr, &mut attributes) - } else if attr.path.is_ident("replace_generics") { + } else if attr.path.is_ident("infer") { parse_replace_generics(attr, &mut attributes) } }); @@ -145,15 +145,15 @@ fn parse_setters(attr: &Attribute, attributes: &mut FieldAttributes) { fn parse_replace_generics(attr: &Attribute, attributes: &mut FieldAttributes) { let meta = attr.parse_meta().unwrap(); - let mut replace_generics = vec![]; + let mut infer = vec![]; if let Meta::List(l) = meta { let it = l.nested.iter(); it.for_each(|m| { if let NestedMeta::Meta(Meta::Path(p)) = m { if let Some(ident) = p.get_ident() { - replace_generics.push(ident.clone()); + infer.push(ident.clone()); } else { - unimplemented!("Invalid replace_generics, write a type parameter.") + unimplemented!("Invalid infer, write a type parameter.") } } else { unimplemented!("Invalid setter.") @@ -162,7 +162,7 @@ fn parse_replace_generics(attr: &Attribute, attributes: &mut FieldAttributes) { } else { unimplemented!("Invalid setter.") } - attributes.replace_generics = replace_generics; + attributes.infer = infer; } pub fn get_documents(attrs: &[Attribute]) -> Vec { diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index ec428f6..4db8811 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -127,13 +127,13 @@ impl<'a> BuilderFunctions<'a> { let fn_lifetime = self.input.fn_lifetime(); let impl_tokens = self.input.tokenize_impl(&[]); let ty_tokens = self.input.tokenize_types(&[], false); - let ty_tokens_ = self.input.tokenize_types(&f.attrs.replace_generics, false); + let ty_tokens_ = self.input.tokenize_types(&f.attrs.infer, false); let fn_generics = f.tokenize_replacement_params(); - let fn_where_clause = self.input.setter_where_clause(&f.attrs.replace_generics); + let fn_where_clause = self.input.setter_where_clause(&f.attrs.infer); let (other_generics, before_generics, mut after_generics) = self.get_generics(f, index); let replaced_ty = replace_type_params_in( quote! { #orig_ty }, - &f.attrs.replace_generics, + &f.attrs.infer, &ident_add_underscore_tree, ); after_generics @@ -142,7 +142,7 @@ impl<'a> BuilderFunctions<'a> { let tokens = std::mem::take(ty_tokens); *ty_tokens = replace_type_params_in( tokens, - &f.attrs.replace_generics, + &f.attrs.infer, &ident_add_underscore_tree, ); }); diff --git a/builder-pattern-macro/src/field.rs b/builder-pattern-macro/src/field.rs index fcff24b..cd2752f 100644 --- a/builder-pattern-macro/src/field.rs +++ b/builder-pattern-macro/src/field.rs @@ -33,10 +33,10 @@ impl Field { pub fn tokenize_replacement_params(&self) -> TokenStream { let mut stream = TokenStream::new(); - if self.attrs.replace_generics.is_empty() { + if self.attrs.infer.is_empty() { return stream; } - let underscored = self.attrs.replace_generics.iter().map(|ident| { + let underscored = self.attrs.infer.iter().map(|ident| { let string = ident.to_string() + "_"; Ident::new(&string, ident.span()) }); diff --git a/builder-pattern-macro/src/lib.rs b/builder-pattern-macro/src/lib.rs index aa4f38b..ff10e7c 100644 --- a/builder-pattern-macro/src/lib.rs +++ b/builder-pattern-macro/src/lib.rs @@ -31,7 +31,7 @@ extern crate proc_macro2; public, setter, validator, - replace_generics, + infer, ) )] pub fn derive_builder(input: TokenStream) -> TokenStream { diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index c293988..6f47ff0 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -152,7 +152,7 @@ impl StructInput { /// Tokenize type parameters. /// It skips lifetimes and has no outer brackets. - pub fn tokenize_types(&self, replace_generics: &[Ident], omit_replaced: bool) -> TokenStream { + pub fn tokenize_types(&self, infer: &[Ident], omit_replaced: bool) -> TokenStream { let generics = &self.generics; let mut tokens = TokenStream::new(); @@ -161,7 +161,7 @@ impl StructInput { } if omit_replaced && generics.params.iter().all(|x| match x { - GenericParam::Type(param) => replace_generics.contains(¶m.ident), + GenericParam::Type(param) => infer.contains(¶m.ident), GenericParam::Const(_) => false, _ => true, }) @@ -187,7 +187,7 @@ impl StructInput { GenericParam::Lifetime(_) => unreachable!(), GenericParam::Type(param) => { // Leave off the type parameter defaults - if replace_generics.contains(¶m.ident) { + if infer.contains(¶m.ident) { if omit_replaced { continue; } @@ -207,9 +207,9 @@ impl StructInput { tokens } - pub fn setter_where_clause(&self, replace_generics: &[Ident]) -> TokenStream { + pub fn setter_where_clause(&self, infer: &[Ident]) -> TokenStream { let mut stream = TokenStream::new(); - if replace_generics.is_empty() { + if infer.is_empty() { return stream; } let clauses = self diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs index 65cc00b..2e7e2ea 100644 --- a/builder-pattern/examples/default-generics.rs +++ b/builder-pattern/examples/default-generics.rs @@ -3,7 +3,7 @@ use builder_pattern::Builder; #[allow(unused)] #[derive(Builder)] struct Op { - #[replace_generics(T)] + #[infer(T)] #[default(None)] optional_field: Option, } From 9348afb1486554589cdd941757864882d6f8bf72 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 17:09:17 +1000 Subject: [PATCH 04/21] infer with complex examples, fix some commas --- .../src/builder/builder_functions.rs | 7 +- builder-pattern-macro/src/struct_input.rs | 58 ++++---------- builder-pattern/examples/default-generics.rs | 75 +++++++++++++++++-- 3 files changed, 84 insertions(+), 56 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 4db8811..4f95b55 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -140,11 +140,8 @@ impl<'a> BuilderFunctions<'a> { .iter_mut() .for_each(|ty_tokens: &mut TokenStream| { let tokens = std::mem::take(ty_tokens); - *ty_tokens = replace_type_params_in( - tokens, - &f.attrs.infer, - &ident_add_underscore_tree, - ); + *ty_tokens = + replace_type_params_in(tokens, &f.attrs.infer, &ident_add_underscore_tree); }); let (arg_type_gen, arg_type) = if f.attrs.use_into { ( diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index 6f47ff0..02b35d0 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -1,4 +1,7 @@ -use crate::attributes::{ident_add_underscore, FieldAttributes, FieldVisibility}; +use crate::attributes::{ + ident_add_underscore, ident_add_underscore_tree, FieldAttributes, FieldVisibility, +}; +use crate::builder::builder_functions::replace_type_params_in; use crate::builder::{ builder_decl::BuilderDecl, builder_functions::BuilderFunctions, builder_impl::BuilderImpl, }; @@ -8,6 +11,7 @@ use crate::struct_impl::StructImpl; use core::str::FromStr; use proc_macro2::{Ident, Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; +use syn::token::Comma; use syn::{ parse::{Parse, ParseStream, Result}, AttrStyle, Attribute, Data, DeriveInput, Fields, GenericParam, Generics, Lifetime, Token, @@ -217,36 +221,9 @@ impl StructInput { .where_clause .iter() .flat_map(|where_clause: &WhereClause| { - where_clause - .predicates - .iter() - .filter_map(|x: &WherePredicate| match x { - WherePredicate::Type(PredicateType { - bounded_ty: Type::Path(TypePath { qself: None, path }), - bounds, - lifetimes, - colon_token, - .. - }) => { - if let Some(ident) = path.get_ident() { - let ident_ = ident.to_string() + "_"; - let replacement_ident = Ident::new(&ident_, ident.span()); - let pred = WherePredicate::Type(PredicateType { - bounded_ty: Type::Path(TypePath { - qself: None, - path: Path::from(replacement_ident), - }), - bounds: bounds.clone(), - lifetimes: lifetimes.clone(), - colon_token: colon_token.clone(), - }); - Some(quote! { #pred }) - } else { - None - } - } - _ => None, - }) + where_clause.predicates.iter().map(|pred: &WherePredicate| { + replace_type_params_in(quote! { #pred }, infer, &ident_add_underscore_tree) + }) }); stream.extend(quote! { where }); stream.append_terminated(clauses, quote! { , }); @@ -264,27 +241,21 @@ impl StructInput { } if generics.params.iter().all(|x| match x { GenericParam::Type(param) => filter_out.contains(¶m.ident), - GenericParam::Const(_) => false, - _ => true, + _ => false, }) { return tokens; } - let mut trailing_or_empty = true; for param in generics.params.pairs() { - if let GenericParam::Lifetime(_) = **param.value() { - param.to_tokens(&mut tokens); - trailing_or_empty = param.punct().is_some(); + if let GenericParam::Lifetime(l) = *param.value() { + l.to_tokens(&mut tokens); + Comma::default().to_tokens(&mut tokens); } } for param in generics.params.pairs() { if let GenericParam::Lifetime(_) = **param.value() { continue; } - if !trailing_or_empty { - ::default().to_tokens(&mut tokens); - trailing_or_empty = true; - } match *param.value() { GenericParam::Lifetime(_) => unreachable!(), GenericParam::Type(param) => { @@ -317,10 +288,7 @@ impl StructInput { param.ty.to_tokens(&mut tokens); } } - param.punct().to_tokens(&mut tokens); - } - if !tokens.is_empty() { - ::default().to_tokens(&mut tokens); + Comma::default().to_tokens(&mut tokens); } tokens } diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs index 2e7e2ea..ecf79e7 100644 --- a/builder-pattern/examples/default-generics.rs +++ b/builder-pattern/examples/default-generics.rs @@ -1,20 +1,83 @@ use builder_pattern::Builder; +use std::any::{Any, TypeId}; +use std::borrow::Borrow; +use std::marker::PhantomData; #[allow(unused)] #[derive(Builder)] -struct Op { +struct Op { #[infer(T)] #[default(None)] optional_field: Option, } -fn main() { - use std::any::{Any, TypeId}; - // Should be inferred as Op<&'static str>, i.e. the macro should notice the defaulted type param. +fn defaulted() { + // Should be inferred as Op, i.e. the macro should notice the defaulted type param. let a = Op::new().build(); - assert_eq!(Any::type_id(&a), TypeId::of::>()); + assert_eq!(a.type_id(), TypeId::of::>()); +} +fn override_default() { // Should be inferred as Op let a = Op::new().optional_field(Some(5i32)).build(); - assert_eq!(Any::type_id(&a), TypeId::of::>()); + assert_eq!(a.type_id(), TypeId::of::>()); +} + +#[allow(unused)] +#[derive(Builder)] +struct IterExtra> +where + I: IntoIterator, +{ + single: T, + #[default(None)] + extra: Option, +} + +fn inferred() { + let a = IterExtra::new().single(1).build(); + assert_eq!(a.type_id(), TypeId::of::>>()); +} + +#[allow(unused)] +#[derive(Builder)] +struct DefaultedClosure R> +where + F: for<'a> FnMut(R, &T) -> R, +{ + #[infer(F)] + f: Option, + #[hidden] + #[default(PhantomData)] + phantom: PhantomData<(T, R)>, +} + +trait Callable { + fn call_fn(&mut self, r: R, t: &T) -> R; +} +impl Callable for DefaultedClosure +where + F: for<'a> FnMut(R, &T) -> R, +{ + fn call_fn(&mut self, r: R, t: &T) -> R { + if let Some(f) = &mut self.f { + f(r, t) + } else { + r + } + } +} + +fn infer_f_t() { + let mut a = DefaultedClosure::new() + .f(Some(|acc, x: &_| acc + x)) + .build(); + let called: i32 = a.call_fn(5, &5); +} + +fn main() { + defaulted(); + override_default(); + inferred(); + infer_f_t(); } From 4e9cb59774a7aac6894f7cf5a554164d44ad3a1e Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 17:30:07 +1000 Subject: [PATCH 05/21] Recursive replacement of defaulted types in `fn new` block --- builder-pattern-macro/src/struct_impl.rs | 31 +++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 33233df..ffaa1af 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -6,7 +6,7 @@ use crate::{ use core::str::FromStr; use proc_macro2::{Group, Ident, TokenStream, TokenTree}; use quote::ToTokens; -use syn::{parse_quote, spanned::Spanned, Attribute}; +use syn::{parse_quote, spanned::Spanned, Attribute, Generics}; /// Implementation for the given structure. /// It creates a `new` function. @@ -23,38 +23,45 @@ impl<'a> ToTokens for StructImpl<'a> { let lifetimes = self.input.lifetimes(); let empty_generics = self.empty_generics(); let defaulted_generics = self.defaulted_generics(); - let with_param_default = |ident: &Ident| { - self.input - .generics + fn with_param_default( + generics: &Generics, + defaulted_generics: &[Ident], + ident: &Ident, + ) -> TokenTree { + let with_prmdef = + |ident: &Ident| with_param_default(&generics, defaulted_generics, ident); + generics .type_params() .find_map(|x| { if x.ident == *ident { let default = x.default.as_ref().unwrap(); let stream = quote! { #default }; + let replaced_within = + replace_type_params_in(stream, defaulted_generics, &with_prmdef); Some(TokenTree::Group(Group::new( proc_macro2::Delimiter::None, - stream, + replaced_within, ))) } else { None } }) .expect("hmmmm") - }; + } + + let with_prmdef = + |ident: &Ident| with_param_default(&self.input.generics, &defaulted_generics, ident); let impl_tokens = self.input.tokenize_impl(&defaulted_generics); let where_clause = &self.input.generics.where_clause; - let where_tokens = replace_type_params_in( - quote! { #where_clause }, - &defaulted_generics, - &with_param_default, - ); + let where_tokens = + replace_type_params_in(quote! { #where_clause }, &defaulted_generics, &with_prmdef); let ty_tokens = replace_type_params_in( self.input.tokenize_types(&[], false), &defaulted_generics, - &with_param_default, + &with_prmdef, ); let fn_lifetime = self.input.fn_lifetime(); From 1fc75e3b15c3729325e4f4ec4a310b4ae7e26f31 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 18:44:31 +1000 Subject: [PATCH 06/21] full defaulted closures example --- .../src/builder/builder_functions.rs | 6 +- .../src/builder/builder_impl.rs | 50 ++++++--- builder-pattern-macro/src/field.rs | 15 +-- builder-pattern-macro/src/struct_impl.rs | 18 +--- builder-pattern-macro/src/struct_input.rs | 13 +-- builder-pattern/examples/default-generics.rs | 100 +++++++++++++++--- 6 files changed, 140 insertions(+), 62 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 4f95b55..faa9489 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -22,7 +22,11 @@ impl<'a> ToTokens for BuilderFunctions<'a> { .chain(self.input.optional_fields.iter()) .map(|f| { let ident = &f.ident; - quote! { #ident: self.#ident } + if f.attrs.vis == FieldVisibility::Hidden { + quote!{ #ident: None } + } else { + quote! { #ident: self.#ident } + } }) .collect::>(); diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index e5b63f7..e5a55b3 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -3,6 +3,7 @@ use crate::{attributes::Setters, struct_input::StructInput}; use core::str::FromStr; use proc_macro2::TokenStream; use quote::ToTokens; +use syn::spanned::Spanned; pub struct BuilderImpl<'a> { pub input: &'a StructInput, @@ -90,22 +91,38 @@ impl<'a> BuilderImpl<'a> { .for_each(|f| { let ident = &f.ident; struct_init_args.push(ident.to_token_stream()); + let mk_default_case = + |wrap: fn(TokenStream) -> TokenStream| match &f.attrs.default.as_ref() { + Some((expr, setters)) => { + let expr = match *setters { + Setters::VALUE => quote_spanned! { expr.span() => #expr }, + Setters::LAZY => quote_spanned! { expr.span() => (#expr)() }, + _ => unimplemented!(), + }; + let wrapped_expr = wrap(expr); + quote! { None => #wrapped_expr, } + } + None => quote! { None => unreachable!(), }, + }; + if f.attrs.validator.is_some() && !(f.attrs.setters & (Setters::LAZY | Setters::ASYNC)).is_empty() { let async_case = if is_async { quote! { - ::builder_pattern::setter::Setter::Async(f) => Ok(f().await), - ::builder_pattern::setter::Setter::AsyncValidated(f) => f().await, + Some(::builder_pattern::setter::Setter::Async(f)) => Ok(f().await), + Some(::builder_pattern::setter::Setter::AsyncValidated(f)) => f().await, } } else { quote! {_ => unimplemented!()} }; + let default_case = mk_default_case(|expr| quote! { Ok(#expr) }); validated_init_fields.push(quote! { - let #ident = match match self.#ident.unwrap() { - ::builder_pattern::setter::Setter::Value(v) => Ok(v), - ::builder_pattern::setter::Setter::Lazy(f) => Ok(f()), - ::builder_pattern::setter::Setter::LazyValidated(f) => f(), + let #ident = match match self.#ident { + Some(::builder_pattern::setter::Setter::Value(v)) => Ok(v), + Some(::builder_pattern::setter::Setter::Lazy(f)) => Ok(f()), + Some(::builder_pattern::setter::Setter::LazyValidated(f)) => f(), + #default_case #async_case } { Ok(v) => v, @@ -115,32 +132,36 @@ impl<'a> BuilderImpl<'a> { } else { let async_case = if is_async { quote! { - ::builder_pattern::setter::Setter::Async(f) => f().await, + Some(::builder_pattern::setter::Setter::Async(f)) => f().await, _ => unimplemented!(), } } else { quote! {_ => unimplemented!()} }; + let default_case = mk_default_case(|expr| expr); init_fields.push(quote! { - let #ident = match self.#ident.unwrap() { - ::builder_pattern::setter::Setter::Value(v) => v, - ::builder_pattern::setter::Setter::Lazy(f) => f(), + let #ident = match self.#ident { + Some(::builder_pattern::setter::Setter::Value(v)) => v, + Some(::builder_pattern::setter::Setter::Lazy(f)) => f(), + #default_case #async_case }; }); } let async_case = if is_async { quote! { - ::builder_pattern::setter::Setter::Async(f) => f().await, + Some(::builder_pattern::setter::Setter::Async(f)) => f().await, _ => unimplemented!(), } } else { quote! {_ => unimplemented!()} }; + let default_case = mk_default_case(|expr| expr); no_lazy_validation_fields.push(quote! { - let #ident = match self.#ident.unwrap() { - ::builder_pattern::setter::Setter::Value(v) => v, - ::builder_pattern::setter::Setter::Lazy(f) => f(), + let #ident = match self.#ident { + Some(::builder_pattern::setter::Setter::Value(v)) => v, + Some(::builder_pattern::setter::Setter::Lazy(f)) => f(), + #default_case #async_case }; }); @@ -159,6 +180,7 @@ impl<'a> BuilderImpl<'a> { #where_clause { #[allow(dead_code)] + #[allow(clippy::redundant_closure_call)] #vis #kw_async fn build(self) -> #ident <#(#lifetimes,)* #ty_tokens> { #(#no_lazy_validation_fields)* #ident { diff --git a/builder-pattern-macro/src/field.rs b/builder-pattern-macro/src/field.rs index cd2752f..0c8744a 100644 --- a/builder-pattern-macro/src/field.rs +++ b/builder-pattern-macro/src/field.rs @@ -1,9 +1,11 @@ +use crate::attributes::ident_add_underscore; + use super::attributes::FieldAttributes; use core::cmp::Ordering; use proc_macro2::{Ident, TokenStream}; use quote::{ToTokens, TokenStreamExt}; -use syn::{Attribute, Token, Type, Visibility}; +use syn::{token::Comma, Attribute, Token, Type, Visibility}; pub struct Field { pub vis: Visibility, @@ -36,12 +38,13 @@ impl Field { if self.attrs.infer.is_empty() { return stream; } - let underscored = self.attrs.infer.iter().map(|ident| { - let string = ident.to_string() + "_"; - Ident::new(&string, ident.span()) - }); + let underscored = self + .attrs + .infer + .iter() + .map(|ident| ident_add_underscore(ident)); ::default().to_tokens(&mut stream); - stream.append_terminated(underscored, ::default()); + stream.append_terminated(underscored, Comma::default()); ]>::default().to_tokens(&mut stream); stream } diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index ffaa1af..ee79096 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -118,22 +118,8 @@ impl<'a> StructImpl<'a> { } }) .chain(self.input.optional_fields.iter().map(|f| { - if let (ident, Some((expr, setters))) = (&f.ident, &f.attrs.default.as_ref()) { - match *setters { - Setters::VALUE => quote_spanned! { expr.span() => - #ident: Some(::builder_pattern::setter::Setter::Value(#expr)) - }, - Setters::LAZY => { - quote_spanned! { expr.span() => - #ident: Some( - ::builder_pattern::setter::Setter::Lazy( - Box::new(#expr) - ) - ) - } - } - _ => unimplemented!(), - } + if let (ident, Some((expr, _setters))) = (&f.ident, &f.attrs.default.as_ref()) { + quote_spanned! { expr.span() => #ident: None } } else { unimplemented!() } diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index 02b35d0..37ede97 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -173,20 +173,10 @@ impl StructInput { return tokens; } - let mut trailing_or_empty = true; - for param in generics.params.pairs() { - if let GenericParam::Lifetime(_) = *param.value() { - trailing_or_empty = param.punct().is_some(); - } - } for param in generics.params.pairs() { if let GenericParam::Lifetime(_) = **param.value() { continue; } - if !trailing_or_empty { - ::default().to_tokens(&mut tokens); - trailing_or_empty = true; - } match *param.value() { GenericParam::Lifetime(_) => unreachable!(), GenericParam::Type(param) => { @@ -205,9 +195,8 @@ impl StructInput { param.ident.to_tokens(&mut tokens); } } - param.punct().to_tokens(&mut tokens); + Comma::default().to_tokens(&mut tokens); } - ::default().to_tokens(&mut tokens); tokens } diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs index ecf79e7..1229b2d 100644 --- a/builder-pattern/examples/default-generics.rs +++ b/builder-pattern/examples/default-generics.rs @@ -1,7 +1,7 @@ use builder_pattern::Builder; use std::any::{Any, TypeId}; -use std::borrow::Borrow; use std::marker::PhantomData; +use std::ops::Add; #[allow(unused)] #[derive(Builder)] @@ -41,26 +41,34 @@ fn inferred() { #[allow(unused)] #[derive(Builder)] -struct DefaultedClosure R> +struct DefaultedClosure R> where - F: for<'a> FnMut(R, &T) -> R, + F1: for<'a> FnMut(R, &T) -> R, + F2: for<'a> FnMut(R, &T) -> R, { - #[infer(F)] - f: Option, + mandatory: F1, + #[infer(F2)] + #[default(None)] + optional: Option, #[hidden] - #[default(PhantomData)] + #[default_lazy(|| PhantomData)] phantom: PhantomData<(T, R)>, } trait Callable { fn call_fn(&mut self, r: R, t: &T) -> R; + fn call_inverse(&mut self, r: R, t: &T) -> R; } -impl Callable for DefaultedClosure +impl Callable for DefaultedClosure where - F: for<'a> FnMut(R, &T) -> R, + F1: for<'a> FnMut(R, &T) -> R, + F2: for<'a> FnMut(R, &T) -> R, { fn call_fn(&mut self, r: R, t: &T) -> R { - if let Some(f) = &mut self.f { + (self.mandatory)(r, t) + } + fn call_inverse(&mut self, r: R, t: &T) -> R { + if let Some(f) = &mut self.optional { f(r, t) } else { r @@ -68,16 +76,82 @@ where } } -fn infer_f_t() { +fn accumulate_sum(acc: T, next: &T) -> T +where + T: for<'a> Add<&'a T, Output = T>, +{ + acc + next +} + +fn infer_f_generic() { + let mut _a = DefaultedClosure::new() + .mandatory(|acc: f64, x| acc + x) + .optional(Some(accumulate_sum)) + .build(); +} + +fn infer_f_missing() { + let mut _a = DefaultedClosure::new() + .mandatory(|acc: f64, x| acc + x) + .build(); +} + +fn fold_with_closure<'b, F1, T: 'b, R, F2>( + iter: impl Iterator, + init: R, + mut c: DefaultedClosure, +) -> R +where + F1: for<'a> FnMut(R, &T) -> R, + F2: for<'a> FnMut(R, &T) -> R, +{ + iter.fold(init, move |acc, x| c.call_fn(acc, x)) +} + +fn infer_using_fold() { + let _ = fold_with_closure( + core::iter::once(&5i32), + 0, + DefaultedClosure::new().mandatory(|acc, &x| acc + x).build(), + ); +} + +fn infer_before_fold() { + let folder = DefaultedClosure::new().mandatory(|acc, &x| acc + x).build(); + let _ = fold_with_closure(core::iter::once(&5i32), 0, folder); +} + +fn infer_t_r() { + let mut a = DefaultedClosure::new() + // The types of the closure params should be inferred + .mandatory(|acc, x| acc + x) + .build(); + let _called: i32 = a.call_fn(5i32, &5); +} + +fn build_with_optional_new_type() { + let mut captured = String::from("hello"); let mut a = DefaultedClosure::new() - .f(Some(|acc, x: &_| acc + x)) + // The types of the closure params should be inferred + .mandatory(|acc, x| acc + x) + // These ones can't be inferred so easily, apparently. We need to use &_ to force the + // closure to be : for<'a> FnMut(..) + .optional(Some(move |acc, x: &_| { + captured.push_str("hello"); + acc - x + })) .build(); - let called: i32 = a.call_fn(5, &5); + let _called: i32 = a.call_fn(5i32, &5); } fn main() { defaulted(); override_default(); inferred(); - infer_f_t(); + infer_f_generic(); + infer_f_missing(); + infer_using_fold(); + infer_before_fold(); + build_with_optional_new_type(); + infer_t_r(); } From 3e14b13d4ca4c0288494c00e5bfc63f72e5c11da Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 19:06:24 +1000 Subject: [PATCH 07/21] fix warnings --- builder-pattern-macro/src/struct_impl.rs | 5 +---- builder-pattern-macro/src/struct_input.rs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index ee79096..33021c2 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -1,7 +1,4 @@ -use crate::{ - attributes::Setters, builder::builder_functions::replace_type_params_in, - struct_input::StructInput, -}; +use crate::{builder::builder_functions::replace_type_params_in, struct_input::StructInput}; use core::str::FromStr; use proc_macro2::{Group, Ident, TokenStream, TokenTree}; diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index 37ede97..7f4f565 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -17,7 +17,7 @@ use syn::{ AttrStyle, Attribute, Data, DeriveInput, Fields, GenericParam, Generics, Lifetime, Token, VisPublic, Visibility, }; -use syn::{Path, PredicateType, Type, TypePath, WhereClause, WherePredicate}; +use syn::{WhereClause, WherePredicate}; pub struct StructInput { pub vis: Visibility, From 5c0968192f806ef9b7f6a1c8a1aaabde59026954 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 19:14:13 +1000 Subject: [PATCH 08/21] rename _phantom to __builder_phantom --- builder-pattern-macro/src/builder/builder_decl.rs | 2 +- builder-pattern-macro/src/builder/builder_functions.rs | 8 ++++---- builder-pattern-macro/src/struct_impl.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_decl.rs b/builder-pattern-macro/src/builder/builder_decl.rs index 92e904a..1e6cff7 100644 --- a/builder-pattern-macro/src/builder/builder_decl.rs +++ b/builder-pattern-macro/src/builder/builder_decl.rs @@ -41,7 +41,7 @@ impl<'a> ToTokens for BuilderDecl<'a> { AsyncFieldMarker, ValidatorOption > #where_clause { - _phantom: ::core::marker::PhantomData<( + __builder_phantom: ::core::marker::PhantomData<( #ty_tokens #(#all_generics,)* AsyncFieldMarker, diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index faa9489..2cfb474 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -180,7 +180,7 @@ impl<'a> BuilderFunctions<'a> { match #v (value.into()) { Ok(value) => Ok( #builder_name { - _phantom: ::core::marker::PhantomData, + __builder_phantom: ::core::marker::PhantomData, #(#builder_fields),* }), Err(e) => Err(format!("Validation failed: {:?}", e)) @@ -207,7 +207,7 @@ impl<'a> BuilderFunctions<'a> { }, quote! { #builder_name { - _phantom: ::core::marker::PhantomData, + __builder_phantom: ::core::marker::PhantomData, #(#builder_fields),* } }, @@ -284,7 +284,7 @@ impl<'a> BuilderFunctions<'a> { }; let ret_expr_val = quote! { #builder_name { - _phantom: ::core::marker::PhantomData, + __builder_phantom: ::core::marker::PhantomData, #(#builder_fields),* } }; @@ -383,7 +383,7 @@ impl<'a> BuilderFunctions<'a> { }; let ret_expr_val = quote! { #builder_name { - _phantom: ::core::marker::PhantomData, + __builder_phantom: ::core::marker::PhantomData, #(#builder_fields),* } }; diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 33021c2..5efca7c 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -80,7 +80,7 @@ impl<'a> ToTokens for StructImpl<'a> { > { #[allow(clippy::redundant_closure_call)] #builder_name { - _phantom: ::core::marker::PhantomData, + __builder_phantom: ::core::marker::PhantomData, #(#builder_init_args),* } } From 173f355c155612973597d05de5f58783690b6e5b Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 19:23:30 +1000 Subject: [PATCH 09/21] Fix inference problems with defaults (default-fn example) --- .../src/builder/builder_impl.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index e5a55b3..860929e 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -3,7 +3,7 @@ use crate::{attributes::Setters, struct_input::StructInput}; use core::str::FromStr; use proc_macro2::TokenStream; use quote::ToTokens; -use syn::spanned::Spanned; +use syn::{spanned::Spanned, Type}; pub struct BuilderImpl<'a> { pub input: &'a StructInput, @@ -90,16 +90,17 @@ impl<'a> BuilderImpl<'a> { .chain(self.input.optional_fields.iter()) .for_each(|f| { let ident = &f.ident; + let ty = &f.ty; struct_init_args.push(ident.to_token_stream()); let mk_default_case = - |wrap: fn(TokenStream) -> TokenStream| match &f.attrs.default.as_ref() { + |wrap: fn(TokenStream, &Type) -> TokenStream| match &f.attrs.default.as_ref() { Some((expr, setters)) => { let expr = match *setters { Setters::VALUE => quote_spanned! { expr.span() => #expr }, Setters::LAZY => quote_spanned! { expr.span() => (#expr)() }, _ => unimplemented!(), }; - let wrapped_expr = wrap(expr); + let wrapped_expr = wrap(expr, ty); quote! { None => #wrapped_expr, } } None => quote! { None => unreachable!(), }, @@ -116,13 +117,13 @@ impl<'a> BuilderImpl<'a> { } else { quote! {_ => unimplemented!()} }; - let default_case = mk_default_case(|expr| quote! { Ok(#expr) }); + let default_case = mk_default_case(|expr, ty| quote! { Ok((#expr) as #ty) }); validated_init_fields.push(quote! { let #ident = match match self.#ident { + #default_case Some(::builder_pattern::setter::Setter::Value(v)) => Ok(v), Some(::builder_pattern::setter::Setter::Lazy(f)) => Ok(f()), Some(::builder_pattern::setter::Setter::LazyValidated(f)) => f(), - #default_case #async_case } { Ok(v) => v, @@ -138,12 +139,12 @@ impl<'a> BuilderImpl<'a> { } else { quote! {_ => unimplemented!()} }; - let default_case = mk_default_case(|expr| expr); + let default_case = mk_default_case(|expr, ty| quote! { (#expr) as #ty }); init_fields.push(quote! { let #ident = match self.#ident { + #default_case Some(::builder_pattern::setter::Setter::Value(v)) => v, Some(::builder_pattern::setter::Setter::Lazy(f)) => f(), - #default_case #async_case }; }); @@ -156,7 +157,7 @@ impl<'a> BuilderImpl<'a> { } else { quote! {_ => unimplemented!()} }; - let default_case = mk_default_case(|expr| expr); + let default_case = mk_default_case(|expr, ty| quote! { (#expr) as #ty }); no_lazy_validation_fields.push(quote! { let #ident = match self.#ident { Some(::builder_pattern::setter::Setter::Value(v)) => v, From b13532e0d43d5ed1a1d5ed78b084a06b052cf139 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 19:23:42 +1000 Subject: [PATCH 10/21] fix warnings in example code --- builder-pattern/examples/default.rs | 1 + builder-pattern/examples/documentation.rs | 1 + builder-pattern/examples/fail-visibility1.rs | 4 +++- builder-pattern/examples/visibility1.rs | 1 + builder-pattern/examples/visibility2.rs | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/builder-pattern/examples/default.rs b/builder-pattern/examples/default.rs index 306cecf..957e887 100644 --- a/builder-pattern/examples/default.rs +++ b/builder-pattern/examples/default.rs @@ -1,6 +1,7 @@ use builder_pattern::Builder; use uuid::Uuid; +#[allow(unused)] #[derive(Builder, Debug)] struct Test { #[default(String::from("Jack"))] diff --git a/builder-pattern/examples/documentation.rs b/builder-pattern/examples/documentation.rs index 6417460..92008cb 100644 --- a/builder-pattern/examples/documentation.rs +++ b/builder-pattern/examples/documentation.rs @@ -17,6 +17,7 @@ use builder_pattern::Builder; /// /// println!("{:?}", person); /// ``` +#[allow(unused)] #[derive(Builder, Debug)] struct Person { /** diff --git a/builder-pattern/examples/fail-visibility1.rs b/builder-pattern/examples/fail-visibility1.rs index cbd05e4..eaa1020 100644 --- a/builder-pattern/examples/fail-visibility1.rs +++ b/builder-pattern/examples/fail-visibility1.rs @@ -3,13 +3,15 @@ mod test { // Private structure #[derive(Builder, Debug)] - struct PrivateTest { + pub struct PrivateTest { pub a: i32, pub b: &'static str, c: i32, } } +use test::*; + pub fn main() { let t1 = PrivateTest::new().a(333).c(1.234).b("hello").build(); } diff --git a/builder-pattern/examples/visibility1.rs b/builder-pattern/examples/visibility1.rs index 3a4b8bf..b2a6407 100644 --- a/builder-pattern/examples/visibility1.rs +++ b/builder-pattern/examples/visibility1.rs @@ -2,6 +2,7 @@ mod test { use builder_pattern::Builder; // Public structure + #[allow(unused)] #[derive(Builder, Debug)] pub struct PublicTest { pub a: i32, diff --git a/builder-pattern/examples/visibility2.rs b/builder-pattern/examples/visibility2.rs index c8f961a..36d871b 100644 --- a/builder-pattern/examples/visibility2.rs +++ b/builder-pattern/examples/visibility2.rs @@ -3,6 +3,7 @@ mod test { // Public structure #[derive(Builder, Debug)] + #[allow(unused)] pub struct PublicTest { pub a: i32, pub b: Option, From deb9b32990521ac3e4c51372907f648168206050 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 19:57:02 +1000 Subject: [PATCH 11/21] make into work with default type params + infer --- .../src/builder/builder_functions.rs | 20 ++++++++------- builder-pattern-macro/src/field.rs | 5 ++-- builder-pattern/examples/into-with-default.rs | 25 +++++++++++++++++++ 3 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 builder-pattern/examples/into-with-default.rs diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 2cfb474..3507d96 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -23,7 +23,7 @@ impl<'a> ToTokens for BuilderFunctions<'a> { .map(|f| { let ident = &f.ident; if f.attrs.vis == FieldVisibility::Hidden { - quote!{ #ident: None } + quote! { #ident: None } } else { quote! { #ident: self.#ident } } @@ -132,7 +132,6 @@ impl<'a> BuilderFunctions<'a> { let impl_tokens = self.input.tokenize_impl(&[]); let ty_tokens = self.input.tokenize_types(&[], false); let ty_tokens_ = self.input.tokenize_types(&f.attrs.infer, false); - let fn_generics = f.tokenize_replacement_params(); let fn_where_clause = self.input.setter_where_clause(&f.attrs.infer); let (other_generics, before_generics, mut after_generics) = self.get_generics(f, index); let replaced_ty = replace_type_params_in( @@ -147,13 +146,16 @@ impl<'a> BuilderFunctions<'a> { *ty_tokens = replace_type_params_in(tokens, &f.attrs.infer, &ident_add_underscore_tree); }); - let (arg_type_gen, arg_type) = if f.attrs.use_into { - ( - quote! {>}, - TokenStream::from_str("IntoType").unwrap(), - ) + let into_generics = if f.attrs.use_into { + vec![quote! {IntoType: Into<#replaced_ty>}] } else { - (fn_generics, quote! { #replaced_ty }) + vec![] + }; + let fn_generics = f.tokenize_replacement_params(&into_generics); + let arg_type = if f.attrs.use_into { + quote! { IntoType } + } else { + quote! { #replaced_ty } }; let documents = Self::documents(f, Setters::VALUE); @@ -233,7 +235,7 @@ impl<'a> BuilderFunctions<'a> { #where_clause { #(#documents)* - #vis fn #ident #arg_type_gen(self, value: #arg_type) -> #ret_type + #vis fn #ident #fn_generics(self, value: #arg_type) -> #ret_type #fn_where_clause { #ret_expr diff --git a/builder-pattern-macro/src/field.rs b/builder-pattern-macro/src/field.rs index 0c8744a..79ca098 100644 --- a/builder-pattern-macro/src/field.rs +++ b/builder-pattern-macro/src/field.rs @@ -33,9 +33,9 @@ impl Field { } } - pub fn tokenize_replacement_params(&self) -> TokenStream { + pub fn tokenize_replacement_params(&self, additional: &[TokenStream]) -> TokenStream { let mut stream = TokenStream::new(); - if self.attrs.infer.is_empty() { + if self.attrs.infer.is_empty() && additional.is_empty() { return stream; } let underscored = self @@ -45,6 +45,7 @@ impl Field { .map(|ident| ident_add_underscore(ident)); ::default().to_tokens(&mut stream); stream.append_terminated(underscored, Comma::default()); + stream.append_terminated(additional.iter(), Comma::default()); ]>::default().to_tokens(&mut stream); stream } diff --git a/builder-pattern/examples/into-with-default.rs b/builder-pattern/examples/into-with-default.rs new file mode 100644 index 0000000..619c88c --- /dev/null +++ b/builder-pattern/examples/into-with-default.rs @@ -0,0 +1,25 @@ +use std::any::TypeId; + +use builder_pattern::Builder; + +#[allow(unused)] +#[derive(Builder)] +struct Test { + // Note that without the #[infer(T)], we would still have T = f64 from the + // type param default. + // The setter method will have a `T_` parameter, and return a TestBuilder. + #[infer(T)] + #[into] + vector: Vec, +} + +fn main() { + let _ = Test::new().vector(&b"byte slice"[..]).build(); + + // in more detail: + // we can't use a mutable builder and re-assign it, because they are different types. + let builder = Test::new(); + let builder = builder.vector::(&b"hello"[..]); + let t = builder.build(); + assert_eq!(std::any::Any::type_id(&t.vector), TypeId::of::>()); +} From cab8bc76dc1b46043bc610b76df5d2c161e7894c Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 19:57:18 +1000 Subject: [PATCH 12/21] add failing test for empty struct --- test-no-future/examples/empty.rs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 test-no-future/examples/empty.rs diff --git a/test-no-future/examples/empty.rs b/test-no-future/examples/empty.rs new file mode 100644 index 0000000..4f5333b --- /dev/null +++ b/test-no-future/examples/empty.rs @@ -0,0 +1,6 @@ +use builder_pattern::Builder; + +#[derive(Builder)] +struct Thing {} + +fn main() {} From d339bd18e41ef05ef088eb087715e1aaed6ad32d Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 20:43:02 +1000 Subject: [PATCH 13/21] make the empty struct case compile --- builder-pattern-macro/src/builder/builder_decl.rs | 1 + builder-pattern-macro/src/builder/builder_impl.rs | 2 +- builder-pattern-macro/src/struct_impl.rs | 2 +- test-no-future/examples/empty.rs | 4 +++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_decl.rs b/builder-pattern-macro/src/builder/builder_decl.rs index 1e6cff7..4656441 100644 --- a/builder-pattern-macro/src/builder/builder_decl.rs +++ b/builder-pattern-macro/src/builder/builder_decl.rs @@ -42,6 +42,7 @@ impl<'a> ToTokens for BuilderDecl<'a> { ValidatorOption > #where_clause { __builder_phantom: ::core::marker::PhantomData<( + &#fn_lifetime (), #ty_tokens #(#all_generics,)* AsyncFieldMarker, diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 860929e..40d1889 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -177,7 +177,7 @@ impl<'a> BuilderImpl<'a> { }; tokens.extend(quote! { impl <#fn_lifetime, #impl_tokens #(#optional_generics,)*> #builder_name - <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#satisfied_generics),*, #async_generic, ()> + <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#satisfied_generics,)* #async_generic, ()> #where_clause { #[allow(dead_code)] diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 5efca7c..550e66a 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -74,7 +74,7 @@ impl<'a> ToTokens for StructImpl<'a> { #fn_lifetime, #(#lifetimes,)* #ty_tokens - #(#empty_generics),*, + #(#empty_generics,)* (), () > { diff --git a/test-no-future/examples/empty.rs b/test-no-future/examples/empty.rs index 4f5333b..ddff689 100644 --- a/test-no-future/examples/empty.rs +++ b/test-no-future/examples/empty.rs @@ -3,4 +3,6 @@ use builder_pattern::Builder; #[derive(Builder)] struct Thing {} -fn main() {} +fn main() { + let _: Thing = Thing::new().build(); +} From 80b6e4d246d03e53845c6c50dd58602d740d885b Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 22:46:09 +1000 Subject: [PATCH 14/21] wip --- .../src/builder/builder_impl.rs | 23 ++++++--- builder-pattern-macro/src/struct_impl.rs | 47 ++----------------- builder-pattern-macro/src/struct_input.rs | 31 +++++++++++- builder-pattern/examples/default-generics.rs | 19 +++----- 4 files changed, 58 insertions(+), 62 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 40d1889..93819db 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -1,10 +1,12 @@ use crate::{attributes::Setters, struct_input::StructInput}; use core::str::FromStr; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::ToTokens; use syn::{spanned::Spanned, Type}; +use super::builder_functions::replace_type_params_in; + pub struct BuilderImpl<'a> { pub input: &'a StructInput, } @@ -78,6 +80,12 @@ impl<'a> BuilderImpl<'a> { let impl_tokens = self.input.tokenize_impl(&[]); let optional_generics = self.optional_generics().collect::>(); let satisfied_generics = self.satified_generics().collect::>(); + let defaulted_generics = self.input.defaulted_generics(); + + let with_prmdef = |ident: &Ident| self.input.with_param_default(&defaulted_generics, ident); + let replace_defaults = + |stream: TokenStream| replace_type_params_in(stream, &defaulted_generics, &with_prmdef); + let ty_tokens = self.input.tokenize_types(&[], false); let mut struct_init_args = vec![]; @@ -91,17 +99,18 @@ impl<'a> BuilderImpl<'a> { .for_each(|f| { let ident = &f.ident; let ty = &f.ty; + let substituted_ty = replace_defaults(quote! { #ty }); struct_init_args.push(ident.to_token_stream()); let mk_default_case = - |wrap: fn(TokenStream, &Type) -> TokenStream| match &f.attrs.default.as_ref() { + |wrap: fn(TokenStream) -> TokenStream| match &f.attrs.default.as_ref() { Some((expr, setters)) => { let expr = match *setters { Setters::VALUE => quote_spanned! { expr.span() => #expr }, Setters::LAZY => quote_spanned! { expr.span() => (#expr)() }, _ => unimplemented!(), }; - let wrapped_expr = wrap(expr, ty); - quote! { None => #wrapped_expr, } + let wrapped_expr = wrap(expr); + quote! { None => { let val: #substituted_ty = #wrapped_expr; val }, } } None => quote! { None => unreachable!(), }, }; @@ -117,7 +126,7 @@ impl<'a> BuilderImpl<'a> { } else { quote! {_ => unimplemented!()} }; - let default_case = mk_default_case(|expr, ty| quote! { Ok((#expr) as #ty) }); + let default_case = mk_default_case(|expr| quote! { Ok(#expr) }); validated_init_fields.push(quote! { let #ident = match match self.#ident { #default_case @@ -139,7 +148,7 @@ impl<'a> BuilderImpl<'a> { } else { quote! {_ => unimplemented!()} }; - let default_case = mk_default_case(|expr, ty| quote! { (#expr) as #ty }); + let default_case = mk_default_case(|expr| quote! { #expr }); init_fields.push(quote! { let #ident = match self.#ident { #default_case @@ -157,7 +166,7 @@ impl<'a> BuilderImpl<'a> { } else { quote! {_ => unimplemented!()} }; - let default_case = mk_default_case(|expr, ty| quote! { (#expr) as #ty }); + let default_case = mk_default_case(|expr| quote! { #expr }); no_lazy_validation_fields.push(quote! { let #ident = match self.#ident { Some(::builder_pattern::setter::Setter::Value(v)) => v, diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 550e66a..8ff03c0 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -19,35 +19,11 @@ impl<'a> ToTokens for StructImpl<'a> { let lifetimes = self.input.lifetimes(); let empty_generics = self.empty_generics(); - let defaulted_generics = self.defaulted_generics(); - fn with_param_default( - generics: &Generics, - defaulted_generics: &[Ident], - ident: &Ident, - ) -> TokenTree { - let with_prmdef = - |ident: &Ident| with_param_default(&generics, defaulted_generics, ident); - generics - .type_params() - .find_map(|x| { - if x.ident == *ident { - let default = x.default.as_ref().unwrap(); - let stream = quote! { #default }; - let replaced_within = - replace_type_params_in(stream, defaulted_generics, &with_prmdef); - Some(TokenTree::Group(Group::new( - proc_macro2::Delimiter::None, - replaced_within, - ))) - } else { - None - } - }) - .expect("hmmmm") - } + let defaulted_generics = self.input.defaulted_generics(); - let with_prmdef = - |ident: &Ident| with_param_default(&self.input.generics, &defaulted_generics, ident); + let with_prmdef = |ident: &Ident| self.input.with_param_default(&defaulted_generics, ident); + let replace_defaults = + |stream: TokenStream| replace_type_params_in(stream, &defaulted_generics, &with_prmdef); let impl_tokens = self.input.tokenize_impl(&defaulted_generics); @@ -55,11 +31,7 @@ impl<'a> ToTokens for StructImpl<'a> { let where_tokens = replace_type_params_in(quote! { #where_clause }, &defaulted_generics, &with_prmdef); - let ty_tokens = replace_type_params_in( - self.input.tokenize_types(&[], false), - &defaulted_generics, - &with_prmdef, - ); + let ty_tokens = replace_defaults(self.input.tokenize_types(&[], false)); let fn_lifetime = self.input.fn_lifetime(); @@ -164,13 +136,4 @@ impl<'a> StructImpl<'a> { docs } - - fn defaulted_generics(&self) -> Vec { - self.input - .generics - .type_params() - .filter(|x| x.default.is_some()) - .map(|x| x.ident.clone()) - .collect() - } } diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index 7f4f565..ecd272e 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -9,7 +9,7 @@ use crate::field::Field; use crate::struct_impl::StructImpl; use core::str::FromStr; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree}; use quote::{ToTokens, TokenStreamExt}; use syn::token::Comma; use syn::{ @@ -281,4 +281,33 @@ impl StructInput { } tokens } + + pub fn defaulted_generics(&self) -> Vec { + self.generics + .type_params() + .filter(|x| x.default.is_some()) + .map(|x| x.ident.clone()) + .collect() + } + + pub fn with_param_default(&self, defaulted_generics: &[Ident], ident: &Ident) -> TokenTree { + let with_prmdef = |ident: &Ident| self.with_param_default(defaulted_generics, ident); + self.generics + .type_params() + .find_map(|x| { + if x.ident == *ident { + let default = x.default.as_ref().unwrap(); + let stream = quote! { #default }; + let replaced_within = + replace_type_params_in(stream, defaulted_generics, &with_prmdef); + Some(TokenTree::Group(Group::new( + proc_macro2::Delimiter::None, + replaced_within, + ))) + } else { + None + } + }) + .expect("hmmmm") + } } diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs index 1229b2d..1d05911 100644 --- a/builder-pattern/examples/default-generics.rs +++ b/builder-pattern/examples/default-generics.rs @@ -48,8 +48,8 @@ where { mandatory: F1, #[infer(F2)] - #[default(None)] - optional: Option, + #[default(|r, _t| r)] + optional: F2, #[hidden] #[default_lazy(|| PhantomData)] phantom: PhantomData<(T, R)>, @@ -68,11 +68,8 @@ where (self.mandatory)(r, t) } fn call_inverse(&mut self, r: R, t: &T) -> R { - if let Some(f) = &mut self.optional { - f(r, t) - } else { - r - } + let f = &mut self.optional; + f(r, t) } } @@ -86,7 +83,7 @@ where fn infer_f_generic() { let mut _a = DefaultedClosure::new() .mandatory(|acc: f64, x| acc + x) - .optional(Some(accumulate_sum)) + .optional(accumulate_sum) .build(); } @@ -134,12 +131,10 @@ fn build_with_optional_new_type() { let mut a = DefaultedClosure::new() // The types of the closure params should be inferred .mandatory(|acc, x| acc + x) - // These ones can't be inferred so easily, apparently. We need to use &_ to force the - // closure to be : for<'a> FnMut(..) - .optional(Some(move |acc, x: &_| { + .optional(move |acc, x| { captured.push_str("hello"); acc - x - })) + }) .build(); let _called: i32 = a.call_fn(5i32, &5); } From 44a4fc1146e300bb2ea25b04ea14abb3257aa218 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 23:02:15 +1000 Subject: [PATCH 15/21] late_bound_default --- builder-pattern-macro/src/attributes.rs | 4 +++ .../src/builder/builder_functions.rs | 2 +- .../src/builder/builder_impl.rs | 4 +-- builder-pattern-macro/src/struct_impl.rs | 27 ++++++++++++++++--- builder-pattern/examples/default-generics.rs | 2 +- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/builder-pattern-macro/src/attributes.rs b/builder-pattern-macro/src/attributes.rs index 00eb878..8c9cfb1 100644 --- a/builder-pattern-macro/src/attributes.rs +++ b/builder-pattern-macro/src/attributes.rs @@ -24,6 +24,7 @@ pub struct FieldAttributes { pub documents: Vec, pub setters: Setters, pub vis: FieldVisibility, + pub late_bound_default: bool, pub infer: Vec, } @@ -45,6 +46,7 @@ impl Default for FieldAttributes { documents: vec![], setters: Setters::VALUE, vis: FieldVisibility::Default, + late_bound_default: false, infer: vec![], } } @@ -74,6 +76,7 @@ impl From> for FieldAttributes { unimplemented!("Duplicated `hidden` attributes.") } attributes.vis = FieldVisibility::Hidden; + attributes.late_bound_default = true; } else if attr.path.is_ident("public") { if attributes.vis != FieldVisibility::Default { unimplemented!("Duplicated `public` attributes.") @@ -90,6 +93,7 @@ impl From> for FieldAttributes { } else if attr.path.is_ident("infer") { parse_replace_generics(attr, &mut attributes) } + // TODO: rebind default (syntactically) in infer() setters }); match attributes.validate() { Ok(_) => attributes, diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 3507d96..775da3f 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -22,7 +22,7 @@ impl<'a> ToTokens for BuilderFunctions<'a> { .chain(self.input.optional_fields.iter()) .map(|f| { let ident = &f.ident; - if f.attrs.vis == FieldVisibility::Hidden { + if f.attrs.late_bound_default { quote! { #ident: None } } else { quote! { #ident: self.#ident } diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 93819db..14a7aea 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -103,7 +103,7 @@ impl<'a> BuilderImpl<'a> { struct_init_args.push(ident.to_token_stream()); let mk_default_case = |wrap: fn(TokenStream) -> TokenStream| match &f.attrs.default.as_ref() { - Some((expr, setters)) => { + Some((expr, setters)) if f.attrs.late_bound_default => { let expr = match *setters { Setters::VALUE => quote_spanned! { expr.span() => #expr }, Setters::LAZY => quote_spanned! { expr.span() => (#expr)() }, @@ -112,7 +112,7 @@ impl<'a> BuilderImpl<'a> { let wrapped_expr = wrap(expr); quote! { None => { let val: #substituted_ty = #wrapped_expr; val }, } } - None => quote! { None => unreachable!(), }, + _ => quote! { None => unreachable!("required field not set"), }, }; if f.attrs.validator.is_some() diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 8ff03c0..e943144 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -1,4 +1,7 @@ -use crate::{builder::builder_functions::replace_type_params_in, struct_input::StructInput}; +use crate::{ + attributes::Setters, builder::builder_functions::replace_type_params_in, + struct_input::StructInput, +}; use core::str::FromStr; use proc_macro2::{Group, Ident, TokenStream, TokenTree}; @@ -87,8 +90,26 @@ impl<'a> StructImpl<'a> { } }) .chain(self.input.optional_fields.iter().map(|f| { - if let (ident, Some((expr, _setters))) = (&f.ident, &f.attrs.default.as_ref()) { - quote_spanned! { expr.span() => #ident: None } + if let (ident, Some((expr, setters))) = (&f.ident, &f.attrs.default.as_ref()) { + if f.attrs.late_bound_default { + quote_spanned! { expr.span() => #ident: None } + } else { + match *setters { + Setters::VALUE => quote_spanned! { expr.span() => + #ident: Some(::builder_pattern::setter::Setter::Value(#expr)) + }, + Setters::LAZY => { + quote_spanned! { expr.span() => + #ident: Some( + ::builder_pattern::setter::Setter::Lazy( + Box::new(#expr) + ) + ) + } + } + _ => unimplemented!(), + } + } } else { unimplemented!() } diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs index 1d05911..ba4e0f6 100644 --- a/builder-pattern/examples/default-generics.rs +++ b/builder-pattern/examples/default-generics.rs @@ -51,7 +51,7 @@ where #[default(|r, _t| r)] optional: F2, #[hidden] - #[default_lazy(|| PhantomData)] + #[default(PhantomData)] phantom: PhantomData<(T, R)>, } From 656ba0c1579652436055e35bee144872ec293156 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Sun, 7 May 2023 23:42:38 +1000 Subject: [PATCH 16/21] reflection --- .../src/builder/builder_impl.rs | 14 ++++- builder-pattern-macro/src/struct_impl.rs | 5 +- builder-pattern-macro/src/struct_input.rs | 10 +++- builder-pattern/examples/default-generics.rs | 5 +- builder-pattern/examples/default-infer.rs | 19 ++++++ builder-pattern/src/lib.rs | 3 + builder-pattern/src/refl.rs | 60 +++++++++++++++++++ builder-pattern/src/setter.rs | 10 +++- 8 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 builder-pattern/examples/default-infer.rs create mode 100644 builder-pattern/src/refl.rs diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 14a7aea..25d1d31 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -110,7 +110,19 @@ impl<'a> BuilderImpl<'a> { _ => unimplemented!(), }; let wrapped_expr = wrap(expr); - quote! { None => { let val: #substituted_ty = #wrapped_expr; val }, } + quote! { + None => { let val: #ty = #wrapped_expr; val }, + Some(::builder_pattern::setter::Setter::Default(..)) => + unreachable!("late-bound optional field was set in new()"), + } + } + Some((_expr, _setters)) => { + quote! { + None => unreachable!("early-bound optional field had no default set in new()"), + Some(::builder_pattern::setter::Setter::Default(t, id_t_d)) => { + t + } + } } _ => quote! { None => unreachable!("required field not set"), }, }; diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index e943144..0452ad5 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -96,7 +96,10 @@ impl<'a> StructImpl<'a> { } else { match *setters { Setters::VALUE => quote_spanned! { expr.span() => - #ident: Some(::builder_pattern::setter::Setter::Value(#expr)) + #ident: Some(::builder_pattern::setter::Setter::Default( + #expr, + ::builder_pattern::refl::refl() + )) }, Setters::LAZY => { quote_spanned! { expr.span() => diff --git a/builder-pattern-macro/src/struct_input.rs b/builder-pattern-macro/src/struct_input.rs index ecd272e..7adc36b 100644 --- a/builder-pattern-macro/src/struct_input.rs +++ b/builder-pattern-macro/src/struct_input.rs @@ -143,13 +143,21 @@ impl StructInput { &'a self, fn_lifetime: &'a Lifetime, ) -> impl 'a + Iterator { + // TODO: just store defaulted_generics in a field + let defaulted_generics = self.defaulted_generics(); + let defaulted_generics2 = defaulted_generics.clone(); + let with_prmdef = move |ident: &Ident| self.with_param_default(&defaulted_generics, ident); + let replace_defaults = move |stream: TokenStream| { + replace_type_params_in(stream, &defaulted_generics2, &with_prmdef) + }; self.required_fields .iter() .chain(self.optional_fields.iter()) .map(move |f| { let (ident, ty) = (&f.ident, &f.ty); + let subst = replace_defaults(quote! { #ty }); quote! { - #ident: Option<::builder_pattern::setter::Setter<#fn_lifetime, #ty>> + #ident: Option<::builder_pattern::setter::Setter<#fn_lifetime, #ty, #subst>> } }) } diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs index ba4e0f6..8671417 100644 --- a/builder-pattern/examples/default-generics.rs +++ b/builder-pattern/examples/default-generics.rs @@ -109,7 +109,10 @@ fn infer_using_fold() { let _ = fold_with_closure( core::iter::once(&5i32), 0, - DefaultedClosure::new().mandatory(|acc, &x| acc + x).build(), + DefaultedClosure::new() + .mandatory(|acc, &x| acc + x) + .optional(|_acc, &x| x) + .build(), ); } diff --git a/builder-pattern/examples/default-infer.rs b/builder-pattern/examples/default-infer.rs new file mode 100644 index 0000000..969d943 --- /dev/null +++ b/builder-pattern/examples/default-infer.rs @@ -0,0 +1,19 @@ +use builder_pattern::Builder; +use std::marker::PhantomData; + +#[allow(unused)] +#[derive(Builder)] +struct Inferred { + #[infer(A)] + a: A, + #[infer(B)] + b: B, + // #[hidden] + #[syntactic_default] + #[default(PhantomData)] + b_default: PhantomData, +} + +fn main() { + let i = Inferred::new().a(5i32).b(String::new()).build(); +} diff --git a/builder-pattern/src/lib.rs b/builder-pattern/src/lib.rs index 266ebe1..66f564f 100644 --- a/builder-pattern/src/lib.rs +++ b/builder-pattern/src/lib.rs @@ -648,3 +648,6 @@ pub use builder_pattern_macro::Builder; #[doc(hidden)] pub mod setter; + +#[doc(hidden)] +pub mod refl; diff --git a/builder-pattern/src/refl.rs b/builder-pattern/src/refl.rs new file mode 100644 index 0000000..08f96c1 --- /dev/null +++ b/builder-pattern/src/refl.rs @@ -0,0 +1,60 @@ +//! https://github.com/Centril/refl +//! +//! Used under the MIT license. Just the basics. + +use core::marker::PhantomData; +use core::mem; + +/// +/// ```compile_fail +/// use builder_pattern::refl::Id; +/// let id = Id::>::REFL; +/// ``` +/// +/// ``` +/// use builder_pattern::refl::{refl, Id}; +/// fn get_i32(t: T, id: Id) -> i32 { +/// id.cast(t) +/// } +/// let five = get_i32(5, refl()); +/// assert_eq!(five, 5i32); +/// ``` +/// +pub struct Id(PhantomData<(fn(S) -> S, fn(T) -> T)>); + +impl Id { + pub const REFL: Self = Id(PhantomData); +} + +pub fn refl() -> Id { + Id::REFL +} + +impl Id { + /// Casts a value of type `S` to `T`. + /// + /// This is safe because the `Id` type is always guaranteed to + /// only be inhabited by `Id` types by construction. + pub fn cast(self, value: S) -> T + where + S: Sized, + T: Sized, + { + unsafe { + // Transmute the value; + // This is safe since we know by construction that + // S == T (including lifetime invariance) always holds. + let cast_value = mem::transmute_copy(&value); + + // Forget the value; + // otherwise the destructor of S would be run. + mem::forget(value); + + cast_value + } + } + /// Converts `Id` into `Id` since type equality is symmetric. + pub fn sym(self) -> Id { + Id(PhantomData) + } +} diff --git a/builder-pattern/src/setter.rs b/builder-pattern/src/setter.rs index 9ca8312..81ba79f 100644 --- a/builder-pattern/src/setter.rs +++ b/builder-pattern/src/setter.rs @@ -1,7 +1,15 @@ #[cfg(feature = "future")] use futures::future::LocalBoxFuture; -pub enum Setter<'a, T> { +use super::refl::Id; + +pub enum Setter<'a, T, D = T> { + // Initially, T = D. + // If you set a value with an #[infer(T)] setter, + // then T gets replaced with an inferred type, and we + // no longer _know_ that T = D. It could still be. + // But we will store a Setter::Value, so the Id is gone. + Default(T, Id), Value(T), Lazy(Box T>), LazyValidated(Box Result>), From ee5292b7b5246d8dac12f9b1eccdf98e92b82047 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Mon, 8 May 2023 00:21:30 +1000 Subject: [PATCH 17/21] throw away probs --- builder-pattern-macro/src/attributes.rs | 39 ++++++++++++++++--- .../src/builder/builder_functions.rs | 22 +++++++++++ .../src/builder/builder_impl.rs | 6 +-- builder-pattern-macro/src/lib.rs | 2 + builder-pattern-macro/src/struct_impl.rs | 2 +- builder-pattern/examples/default-infer.rs | 11 +++--- builder-pattern/src/setter.rs | 7 +--- 7 files changed, 66 insertions(+), 23 deletions(-) diff --git a/builder-pattern-macro/src/attributes.rs b/builder-pattern-macro/src/attributes.rs index 8c9cfb1..a0476f0 100644 --- a/builder-pattern-macro/src/attributes.rs +++ b/builder-pattern-macro/src/attributes.rs @@ -25,6 +25,7 @@ pub struct FieldAttributes { pub setters: Setters, pub vis: FieldVisibility, pub late_bound_default: bool, + pub use_inferred: Vec, pub infer: Vec, } @@ -47,6 +48,7 @@ impl Default for FieldAttributes { setters: Setters::VALUE, vis: FieldVisibility::Default, late_bound_default: false, + use_inferred: vec![], infer: vec![], } } @@ -91,9 +93,12 @@ impl From> for FieldAttributes { } else if attr.path.is_ident("setter") { parse_setters(attr, &mut attributes) } else if attr.path.is_ident("infer") { - parse_replace_generics(attr, &mut attributes) + parse_infer(attr, &mut attributes) + } else if attr.path.is_ident("use_inferred") { + parse_use_inferred(attr, &mut attributes) + } else if attr.path.is_ident("late_bound_default") { + attributes.late_bound_default = true; } - // TODO: rebind default (syntactically) in infer() setters }); match attributes.validate() { Ok(_) => attributes, @@ -147,15 +152,15 @@ fn parse_setters(attr: &Attribute, attributes: &mut FieldAttributes) { attributes.setters = setters; } -fn parse_replace_generics(attr: &Attribute, attributes: &mut FieldAttributes) { +fn parse_infer(attr: &Attribute, attributes: &mut FieldAttributes) { let meta = attr.parse_meta().unwrap(); - let mut infer = vec![]; + let mut params = vec![]; if let Meta::List(l) = meta { let it = l.nested.iter(); it.for_each(|m| { if let NestedMeta::Meta(Meta::Path(p)) = m { if let Some(ident) = p.get_ident() { - infer.push(ident.clone()); + params.push(ident.clone()); } else { unimplemented!("Invalid infer, write a type parameter.") } @@ -166,7 +171,29 @@ fn parse_replace_generics(attr: &Attribute, attributes: &mut FieldAttributes) { } else { unimplemented!("Invalid setter.") } - attributes.infer = infer; + attributes.infer = params; +} + +fn parse_use_inferred(attr: &Attribute, attributes: &mut FieldAttributes) { + let meta = attr.parse_meta().unwrap(); + let mut params = vec![]; + if let Meta::List(l) = meta { + let it = l.nested.iter(); + it.for_each(|m| { + if let NestedMeta::Meta(Meta::Path(p)) = m { + if let Some(ident) = p.get_ident() { + params.push(ident.clone()); + } else { + unimplemented!("Invalid use_infer, write a type parameter.") + } + } else { + unimplemented!("Invalid setter.") + } + }); + } else { + unimplemented!("Invalid setter.") + } + attributes.use_inferred = params; } pub fn get_documents(attrs: &[Attribute]) -> Vec { diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 775da3f..358a4e4 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -24,6 +24,28 @@ impl<'a> ToTokens for BuilderFunctions<'a> { let ident = &f.ident; if f.attrs.late_bound_default { quote! { #ident: None } + } else if !f.attrs.use_inferred.is_empty() { + quote! { #ident: None } + // let Some((expr, _)) = f.attrs.default.as_ref() else { + // return quote!{ #ident: compile_error!("#[use_inferred] without #[default]"), }; + // }; + + // rebind to the #[infer]red type by stamping out the default's syntactic + // representation again in this setter method. e.g. `None` can fit in a slot + // for many different Option types. If you change `T` from the default (e.g. + // f64) to an #[infer]red type (e.g. i32) then you as long as the macro writes + // `None` again, we're good. + // quote! { + // #ident: match self.#ident { + // Some(::builder_pattern::setter::Setter::Default(_)) => { + // Some(::builder_pattern::setter::Setter::Value(#expr)) + // } + // Some(::builder_pattern::setter::Setter::Value(val)) => { + // Some(::builder_pattern::setter::Setter::Value(val)) + // } + // _ => unreachable!(), + // } + // } } else { quote! { #ident: self.#ident } } diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 25d1d31..3d49b40 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -111,7 +111,7 @@ impl<'a> BuilderImpl<'a> { }; let wrapped_expr = wrap(expr); quote! { - None => { let val: #ty = #wrapped_expr; val }, + None => { let val: #substituted_ty = #wrapped_expr; val }, Some(::builder_pattern::setter::Setter::Default(..)) => unreachable!("late-bound optional field was set in new()"), } @@ -119,9 +119,7 @@ impl<'a> BuilderImpl<'a> { Some((_expr, _setters)) => { quote! { None => unreachable!("early-bound optional field had no default set in new()"), - Some(::builder_pattern::setter::Setter::Default(t, id_t_d)) => { - t - } + Some(::builder_pattern::setter::Setter::Default(d)) => d, } } _ => quote! { None => unreachable!("required field not set"), }, diff --git a/builder-pattern-macro/src/lib.rs b/builder-pattern-macro/src/lib.rs index ff10e7c..d713f41 100644 --- a/builder-pattern-macro/src/lib.rs +++ b/builder-pattern-macro/src/lib.rs @@ -32,6 +32,8 @@ extern crate proc_macro2; setter, validator, infer, + use_inferred, + late_bound_default, ) )] pub fn derive_builder(input: TokenStream) -> TokenStream { diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index 0452ad5..e06c959 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -98,7 +98,7 @@ impl<'a> StructImpl<'a> { Setters::VALUE => quote_spanned! { expr.span() => #ident: Some(::builder_pattern::setter::Setter::Default( #expr, - ::builder_pattern::refl::refl() + // ::builder_pattern::refl::refl() )) }, Setters::LAZY => { diff --git a/builder-pattern/examples/default-infer.rs b/builder-pattern/examples/default-infer.rs index 969d943..7f51d69 100644 --- a/builder-pattern/examples/default-infer.rs +++ b/builder-pattern/examples/default-infer.rs @@ -3,17 +3,16 @@ use std::marker::PhantomData; #[allow(unused)] #[derive(Builder)] -struct Inferred { +struct Inferred B = fn(B) -> B> { #[infer(A)] a: A, #[infer(B)] b: B, - // #[hidden] - #[syntactic_default] - #[default(PhantomData)] - b_default: PhantomData, + #[late_bound_default] + #[default(|x| x)] + b_default: F, } fn main() { - let i = Inferred::new().a(5i32).b(String::new()).build(); + let i = Inferred::new().b(String::new()).a("5").build(); } diff --git a/builder-pattern/src/setter.rs b/builder-pattern/src/setter.rs index 81ba79f..6bfc475 100644 --- a/builder-pattern/src/setter.rs +++ b/builder-pattern/src/setter.rs @@ -4,12 +4,7 @@ use futures::future::LocalBoxFuture; use super::refl::Id; pub enum Setter<'a, T, D = T> { - // Initially, T = D. - // If you set a value with an #[infer(T)] setter, - // then T gets replaced with an inferred type, and we - // no longer _know_ that T = D. It could still be. - // But we will store a Setter::Value, so the Id is gone. - Default(T, Id), + Default(D), Value(T), Lazy(Box T>), LazyValidated(Box Result>), From 2efabd412f453fce6d7adcd9ef106d78b60223bc Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Mon, 8 May 2023 01:02:01 +1000 Subject: [PATCH 18/21] working --- .../src/builder/builder_functions.rs | 32 ++++++----------- .../src/builder/builder_impl.rs | 9 +++-- builder-pattern-macro/src/struct_impl.rs | 8 +++-- builder-pattern/examples/default-generics.rs | 1 + builder-pattern/examples/default-infer.rs | 36 ++++++++++++++----- builder-pattern/src/setter.rs | 3 +- 6 files changed, 54 insertions(+), 35 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index 358a4e4..ae33ef8 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -23,29 +23,19 @@ impl<'a> ToTokens for BuilderFunctions<'a> { .map(|f| { let ident = &f.ident; if f.attrs.late_bound_default { - quote! { #ident: None } + quote! { + #ident: match self.#ident { + Some(::builder_pattern::setter::Setter::LateBoundDefault(d)) => { + Some(::builder_pattern::setter::Setter::LateBoundDefault(d)) + } + Some(::builder_pattern::setter::Setter::Value(val)) => { + Some(::builder_pattern::setter::Setter::Value(val)) + } + _ => unreachable!(), + } + } } else if !f.attrs.use_inferred.is_empty() { quote! { #ident: None } - // let Some((expr, _)) = f.attrs.default.as_ref() else { - // return quote!{ #ident: compile_error!("#[use_inferred] without #[default]"), }; - // }; - - // rebind to the #[infer]red type by stamping out the default's syntactic - // representation again in this setter method. e.g. `None` can fit in a slot - // for many different Option types. If you change `T` from the default (e.g. - // f64) to an #[infer]red type (e.g. i32) then you as long as the macro writes - // `None` again, we're good. - // quote! { - // #ident: match self.#ident { - // Some(::builder_pattern::setter::Setter::Default(_)) => { - // Some(::builder_pattern::setter::Setter::Value(#expr)) - // } - // Some(::builder_pattern::setter::Setter::Value(val)) => { - // Some(::builder_pattern::setter::Setter::Value(val)) - // } - // _ => unreachable!(), - // } - // } } else { quote! { #ident: self.#ident } } diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 3d49b40..c719cd1 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -99,7 +99,7 @@ impl<'a> BuilderImpl<'a> { .for_each(|f| { let ident = &f.ident; let ty = &f.ty; - let substituted_ty = replace_defaults(quote! { #ty }); + // let substituted_ty = replace_defaults(quote! { #ty }); struct_init_args.push(ident.to_token_stream()); let mk_default_case = |wrap: fn(TokenStream) -> TokenStream| match &f.attrs.default.as_ref() { @@ -111,7 +111,10 @@ impl<'a> BuilderImpl<'a> { }; let wrapped_expr = wrap(expr); quote! { - None => { let val: #substituted_ty = #wrapped_expr; val }, + None => unreachable!("field should have had default"), + Some(::builder_pattern::setter::Setter::LateBoundDefault(id)) => { + { let val: #ty = id.cast(#wrapped_expr); val } + } Some(::builder_pattern::setter::Setter::Default(..)) => unreachable!("late-bound optional field was set in new()"), } @@ -119,7 +122,7 @@ impl<'a> BuilderImpl<'a> { Some((_expr, _setters)) => { quote! { None => unreachable!("early-bound optional field had no default set in new()"), - Some(::builder_pattern::setter::Setter::Default(d)) => d, + Some(::builder_pattern::setter::Setter::Default(d, id)) => id.cast(d), } } _ => quote! { None => unreachable!("required field not set"), }, diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index e06c959..b1101a6 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -92,13 +92,17 @@ impl<'a> StructImpl<'a> { .chain(self.input.optional_fields.iter().map(|f| { if let (ident, Some((expr, setters))) = (&f.ident, &f.attrs.default.as_ref()) { if f.attrs.late_bound_default { - quote_spanned! { expr.span() => #ident: None } + quote_spanned! { expr.span() => + #ident: Some(::builder_pattern::setter::Setter::LateBoundDefault( + ::builder_pattern::refl::refl() + )) + } } else { match *setters { Setters::VALUE => quote_spanned! { expr.span() => #ident: Some(::builder_pattern::setter::Setter::Default( #expr, - // ::builder_pattern::refl::refl() + ::builder_pattern::refl::refl() )) }, Setters::LAZY => { diff --git a/builder-pattern/examples/default-generics.rs b/builder-pattern/examples/default-generics.rs index 8671417..9b5ffa0 100644 --- a/builder-pattern/examples/default-generics.rs +++ b/builder-pattern/examples/default-generics.rs @@ -48,6 +48,7 @@ where { mandatory: F1, #[infer(F2)] + #[late_bound_default] #[default(|r, _t| r)] optional: F2, #[hidden] diff --git a/builder-pattern/examples/default-infer.rs b/builder-pattern/examples/default-infer.rs index 7f51d69..a151abc 100644 --- a/builder-pattern/examples/default-infer.rs +++ b/builder-pattern/examples/default-infer.rs @@ -1,18 +1,38 @@ use builder_pattern::Builder; -use std::marker::PhantomData; #[allow(unused)] #[derive(Builder)] -struct Inferred B = fn(B) -> B> { - #[infer(A)] - a: A, - #[infer(B)] - b: B, +struct LateBound B = fn(B) -> B> { + field_a: A, + field_b: B, #[late_bound_default] #[default(|x| x)] - b_default: F, + transform_b: F, +} + +impl LateBound +where + B: Clone, +{ + fn get_b(&self) -> B { + (self.transform_b)(self.field_b.clone()) + } +} + +fn with() { + let l = LateBound::new() + .field_a(String::new()) + .field_b(5) + .transform_b(|x| x + 10) + .build(); + assert_eq!(l.get_b(), 15); +} +fn without() { + let l = LateBound::new().field_a(String::new()).field_b(200).build(); + assert_eq!(l.get_b(), 200); } fn main() { - let i = Inferred::new().b(String::new()).a("5").build(); + with(); + without(); } diff --git a/builder-pattern/src/setter.rs b/builder-pattern/src/setter.rs index 6bfc475..73cce48 100644 --- a/builder-pattern/src/setter.rs +++ b/builder-pattern/src/setter.rs @@ -4,7 +4,8 @@ use futures::future::LocalBoxFuture; use super::refl::Id; pub enum Setter<'a, T, D = T> { - Default(D), + Default(D, Id), + LateBoundDefault(Id), Value(T), Lazy(Box T>), LazyValidated(Box Result>), From 01b808bb9334b20b356b8a8cc7e10daa9b32d6f6 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Mon, 8 May 2023 01:02:53 +1000 Subject: [PATCH 19/21] remove use_inferred --- builder-pattern-macro/src/attributes.rs | 26 ------------------- .../src/builder/builder_functions.rs | 2 -- builder-pattern-macro/src/lib.rs | 1 - 3 files changed, 29 deletions(-) diff --git a/builder-pattern-macro/src/attributes.rs b/builder-pattern-macro/src/attributes.rs index a0476f0..bfb5260 100644 --- a/builder-pattern-macro/src/attributes.rs +++ b/builder-pattern-macro/src/attributes.rs @@ -25,7 +25,6 @@ pub struct FieldAttributes { pub setters: Setters, pub vis: FieldVisibility, pub late_bound_default: bool, - pub use_inferred: Vec, pub infer: Vec, } @@ -48,7 +47,6 @@ impl Default for FieldAttributes { setters: Setters::VALUE, vis: FieldVisibility::Default, late_bound_default: false, - use_inferred: vec![], infer: vec![], } } @@ -94,8 +92,6 @@ impl From> for FieldAttributes { parse_setters(attr, &mut attributes) } else if attr.path.is_ident("infer") { parse_infer(attr, &mut attributes) - } else if attr.path.is_ident("use_inferred") { - parse_use_inferred(attr, &mut attributes) } else if attr.path.is_ident("late_bound_default") { attributes.late_bound_default = true; } @@ -174,28 +170,6 @@ fn parse_infer(attr: &Attribute, attributes: &mut FieldAttributes) { attributes.infer = params; } -fn parse_use_inferred(attr: &Attribute, attributes: &mut FieldAttributes) { - let meta = attr.parse_meta().unwrap(); - let mut params = vec![]; - if let Meta::List(l) = meta { - let it = l.nested.iter(); - it.for_each(|m| { - if let NestedMeta::Meta(Meta::Path(p)) = m { - if let Some(ident) = p.get_ident() { - params.push(ident.clone()); - } else { - unimplemented!("Invalid use_infer, write a type parameter.") - } - } else { - unimplemented!("Invalid setter.") - } - }); - } else { - unimplemented!("Invalid setter.") - } - attributes.use_inferred = params; -} - pub fn get_documents(attrs: &[Attribute]) -> Vec { let mut documents: Vec = vec![]; diff --git a/builder-pattern-macro/src/builder/builder_functions.rs b/builder-pattern-macro/src/builder/builder_functions.rs index ae33ef8..7f8ea0a 100644 --- a/builder-pattern-macro/src/builder/builder_functions.rs +++ b/builder-pattern-macro/src/builder/builder_functions.rs @@ -34,8 +34,6 @@ impl<'a> ToTokens for BuilderFunctions<'a> { _ => unreachable!(), } } - } else if !f.attrs.use_inferred.is_empty() { - quote! { #ident: None } } else { quote! { #ident: self.#ident } } diff --git a/builder-pattern-macro/src/lib.rs b/builder-pattern-macro/src/lib.rs index d713f41..d13debc 100644 --- a/builder-pattern-macro/src/lib.rs +++ b/builder-pattern-macro/src/lib.rs @@ -32,7 +32,6 @@ extern crate proc_macro2; setter, validator, infer, - use_inferred, late_bound_default, ) )] From d68d3a6bb184ca001b94e96938ad60ba1e957e4e Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Mon, 8 May 2023 01:04:26 +1000 Subject: [PATCH 20/21] fix unused warnings --- builder-pattern-macro/src/builder/builder_impl.rs | 11 ++--------- builder-pattern-macro/src/struct_impl.rs | 6 +++--- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index c719cd1..5cb9a27 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -1,11 +1,9 @@ use crate::{attributes::Setters, struct_input::StructInput}; use core::str::FromStr; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::TokenStream; use quote::ToTokens; -use syn::{spanned::Spanned, Type}; - -use super::builder_functions::replace_type_params_in; +use syn::spanned::Spanned; pub struct BuilderImpl<'a> { pub input: &'a StructInput, @@ -80,11 +78,6 @@ impl<'a> BuilderImpl<'a> { let impl_tokens = self.input.tokenize_impl(&[]); let optional_generics = self.optional_generics().collect::>(); let satisfied_generics = self.satified_generics().collect::>(); - let defaulted_generics = self.input.defaulted_generics(); - - let with_prmdef = |ident: &Ident| self.input.with_param_default(&defaulted_generics, ident); - let replace_defaults = - |stream: TokenStream| replace_type_params_in(stream, &defaulted_generics, &with_prmdef); let ty_tokens = self.input.tokenize_types(&[], false); diff --git a/builder-pattern-macro/src/struct_impl.rs b/builder-pattern-macro/src/struct_impl.rs index b1101a6..34aea83 100644 --- a/builder-pattern-macro/src/struct_impl.rs +++ b/builder-pattern-macro/src/struct_impl.rs @@ -4,9 +4,9 @@ use crate::{ }; use core::str::FromStr; -use proc_macro2::{Group, Ident, TokenStream, TokenTree}; +use proc_macro2::{Ident, TokenStream}; use quote::ToTokens; -use syn::{parse_quote, spanned::Spanned, Attribute, Generics}; +use syn::{parse_quote, spanned::Spanned, Attribute}; /// Implementation for the given structure. /// It creates a `new` function. @@ -92,7 +92,7 @@ impl<'a> StructImpl<'a> { .chain(self.input.optional_fields.iter().map(|f| { if let (ident, Some((expr, setters))) = (&f.ident, &f.attrs.default.as_ref()) { if f.attrs.late_bound_default { - quote_spanned! { expr.span() => + quote_spanned! { expr.span() => #ident: Some(::builder_pattern::setter::Setter::LateBoundDefault( ::builder_pattern::refl::refl() )) From bb38405f044277f48514ace6ce81117907cb2959 Mon Sep 17 00:00:00 2001 From: Cormac Relf Date: Mon, 8 May 2023 01:12:26 +1000 Subject: [PATCH 21/21] Fix missing match arms for required fields --- builder-pattern-macro/src/builder/builder_impl.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/builder-pattern-macro/src/builder/builder_impl.rs b/builder-pattern-macro/src/builder/builder_impl.rs index 5cb9a27..8cf9e19 100644 --- a/builder-pattern-macro/src/builder/builder_impl.rs +++ b/builder-pattern-macro/src/builder/builder_impl.rs @@ -1,7 +1,7 @@ use crate::{attributes::Setters, struct_input::StructInput}; use core::str::FromStr; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, Span, TokenStream}; use quote::ToTokens; use syn::spanned::Spanned; @@ -113,12 +113,20 @@ impl<'a> BuilderImpl<'a> { } } Some((_expr, _setters)) => { + let id = Ident::new("id", Span::call_site()); + let default = Ident::new("default", Span::call_site()); + let expr = wrap(quote!{ #id.cast(#default) }); quote! { None => unreachable!("early-bound optional field had no default set in new()"), - Some(::builder_pattern::setter::Setter::Default(d, id)) => id.cast(d), + Some(::builder_pattern::setter::Setter::LateBoundDefault(..)) => unreachable!("early-bound optional field had no default set in new()"), + Some(::builder_pattern::setter::Setter::Default(#default, #id)) => #expr, } } - _ => quote! { None => unreachable!("required field not set"), }, + _ => quote! { + Some(::builder_pattern::setter::Setter::LateBoundDefault(..)) | + Some(::builder_pattern::setter::Setter::Default(..)) | + None => unreachable!("required field not set"), + }, }; if f.attrs.validator.is_some()