From e43b15a687e29a7ea4a22e1b22b9749844c60a41 Mon Sep 17 00:00:00 2001 From: Zakarum Date: Mon, 20 Aug 2018 14:15:52 +0300 Subject: [PATCH] Add support for tuple-structs and unit-structs --- examples/example.rs | 42 ++++++++++++++++++++++ src/field_info.rs | 24 +++++++------ src/lib.rs | 14 +++++--- src/struct_info.rs | 86 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 129 insertions(+), 37 deletions(-) diff --git a/examples/example.rs b/examples/example.rs index f5a29126..fd60a666 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -15,6 +15,21 @@ struct Foo { z: i32, } +#[derive(PartialEq, TypedBuilder)] +struct Bar( + i32, + // #[default] without parameter - use the type's default + #[default] + Option, + + // Or you can set the default(encoded as string) + #[default="20"] + i32, +); + +#[derive(PartialEq, TypedBuilder)] +struct Baz; + fn main() { assert!( Foo::builder().x(1).y(2).z(3).build() @@ -35,4 +50,31 @@ fn main() { // This will not compile - because we set y twice: // Foo::builder().x(1).y(2).y(3); + + + assert!( + Bar::builder()._0(1)._1(2)._2(3).build() + == Bar(1, Some(2), 3)); + + // Change the order of construction: + assert!( + Bar::builder()._2(1)._0(2)._1(3).build() + == Bar(2, Some(3), 1)); + + // Optional fields are optional: + assert!( + Bar::builder()._0(1).build() + == Bar(1, None, 20)); + + // This will not compile - because we did not set `0`: + // Bar::builder().build(); + + // This will not compile - because we set `1` twice: + // Bar::builder()._0(1)._1(2)._1(3); + + + assert!( + Baz::builder().build() + == Baz + ); } diff --git a/src/field_info.rs b/src/field_info.rs index 0757a20a..c9bab726 100644 --- a/src/field_info.rs +++ b/src/field_info.rs @@ -5,7 +5,7 @@ use util::{make_identifier, map_only_one}; pub struct FieldInfo<'a> { pub ordinal: usize, - pub name: &'a syn::Ident, + pub name: ::std::borrow::Cow<'a, syn::Ident>, pub generic_ident: syn::Ident, pub ty: &'a syn::Ty, pub default: Option, @@ -13,16 +13,14 @@ pub struct FieldInfo<'a> { impl<'a> FieldInfo<'a> { pub fn new(ordinal: usize, field: &syn::Field) -> FieldInfo { - if let Some(ref name) = field.ident { - FieldInfo { - ordinal: ordinal, - name: &name, - generic_ident: make_identifier("genericType", name), - ty: &field.ty, - default: Self::find_field_default(field).unwrap_or_else(|f| panic!("Field {}: {}", name, f)), - } - } else { - panic!("Nameless field in struct"); + let name = field.ident.as_ref().map(::std::borrow::Cow::Borrowed).unwrap_or_else(|| ::std::borrow::Cow::Owned(format!("_{}", ordinal).into())); + + FieldInfo { + ordinal: ordinal, + generic_ident: make_identifier("genericType", &name), + default: Self::find_field_default(field).unwrap_or_else(|f| panic!("Field {}: {}", name, f)), + name, + ty: &field.ty, } } @@ -44,6 +42,10 @@ impl<'a> FieldInfo<'a> { }) } + pub fn name(&self) -> &syn::Ident { + &self.name + } + pub fn generic_ty_param(&self) -> syn::TyParam { syn::TyParam::from(self.generic_ident.clone()) } diff --git a/src/lib.rs b/src/lib.rs index 909bb84b..ee14abc8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,10 +75,16 @@ pub fn derive_typed_builder(input: TokenStream) -> TokenStream { } fn impl_my_derive(ast: &syn::DeriveInput) -> quote::Tokens { - match ast.body { - syn::Body::Struct(syn::VariantData::Struct(ref body)) => { - let struct_info = struct_info::StructInfo::new(&ast, body); + syn::Body::Struct(ref body) => { + let kind = match *body { + syn::VariantData::Struct(_) => struct_info::StructKind::Struct, + syn::VariantData::Tuple(_) => struct_info::StructKind::Tuple, + syn::VariantData::Unit => struct_info::StructKind::Unit, + }; + + + let struct_info = struct_info::StructInfo::new(&ast, body.fields(), kind); let builder_creation = struct_info.builder_creation_impl(); let conversion_helper = struct_info.conversion_helper_impl(); let fields = struct_info.fields.iter().map(|f| struct_info.field_impl(f)); @@ -90,8 +96,6 @@ fn impl_my_derive(ast: &syn::DeriveInput) -> quote::Tokens { #build_method } }, - syn::Body::Struct(syn::VariantData::Unit) => panic!("SmartBuilder is not supported for unit types"), - syn::Body::Struct(syn::VariantData::Tuple(_)) => panic!("SmartBuilder is not supported for tuples"), syn::Body::Enum(_) => panic!("SmartBuilder is not supported for enums"), } } diff --git a/src/struct_info.rs b/src/struct_info.rs index f62e5e17..46019427 100644 --- a/src/struct_info.rs +++ b/src/struct_info.rs @@ -5,11 +5,18 @@ use quote::Tokens; use field_info::FieldInfo; use util::make_identifier; +pub enum StructKind { + Struct, + Tuple, + Unit, +} + pub struct StructInfo<'a> { pub vis: &'a syn::Visibility, pub name: &'a syn::Ident, pub generics: &'a syn::Generics, pub fields: Vec>, + pub kind: StructKind, pub builder_name: syn::Ident, pub conversion_helper_trait_name: syn::Ident, @@ -17,12 +24,13 @@ pub struct StructInfo<'a> { } impl<'a> StructInfo<'a> { - pub fn new(ast: &'a syn::DeriveInput, fields: &'a [syn::Field]) -> StructInfo<'a> { + pub fn new(ast: &'a syn::DeriveInput, fields: &'a [syn::Field], kind: StructKind) -> StructInfo<'a> { StructInfo { vis: &ast.vis, name: &ast.ident, generics: &ast.generics, fields: fields.iter().enumerate().map(|(i, f)| FieldInfo::new(i, f)).collect(), + kind, builder_name: make_identifier("BuilderFor", &ast.ident), conversion_helper_trait_name: make_identifier("conversionHelperTrait", &ast.ident), conversion_helper_method_name: make_identifier("conversionHelperMethod", &ast.ident), @@ -36,13 +44,13 @@ impl<'a> StructInfo<'a> { } pub fn builder_creation_impl(&self) -> Tokens { - let _ = self.modify_generics(|g| g.ty_params.push(self.fields[0].generic_ty_param())); + let _ = self.modify_generics(|g| self.fields.iter().for_each(|f| g.ty_params.push(f.generic_ty_param()))); let init_empties = { - let names = self.fields.iter().map(|f| f.name); + let names = self.fields.iter().map(|f| f.name()); quote!(#( #names: () ),*) }; let builder_generics = { - let names = self.fields.iter().map(|f| f.name); + let names = self.fields.iter().map(|f| f.name()); let generic_idents = self.fields.iter().map(|f| &f.generic_ident); quote!(#( #names: #generic_idents ),*) }; @@ -105,7 +113,7 @@ impl<'a> StructInfo<'a> { } else { write!(&mut result, ", ").unwrap(); } - write!(&mut result, "`.{}(...)`", field.name).unwrap(); + write!(&mut result, "`.{}(...)`", field.name()).unwrap(); if field.default.is_some() { write!(&mut result, "(optional)").unwrap(); } @@ -144,10 +152,10 @@ impl<'a> StructInfo<'a> { pub fn field_impl(&self, field: &FieldInfo) -> Tokens { let ref builder_name = self.builder_name; let other_fields_name = - self.fields.iter().filter(|f| f.ordinal != field.ordinal).map(|f| f.name); - // not really "value", since we just use to self.name - but close enough. + self.fields.iter().filter(|f| f.ordinal != field.ordinal).map(|f| f.name()); + // not really "value", since we just use to self.name() - but close enough. let other_fields_value = - self.fields.iter().filter(|f| f.ordinal != field.ordinal).map(|f| f.name); + self.fields.iter().filter(|f| f.ordinal != field.ordinal).map(|f| f.name()); let &FieldInfo { name: ref field_name, ty: ref field_type, ref generic_ident, .. } = field; let generics = self.modify_generics(|g| { for f in self.fields.iter() { @@ -232,21 +240,57 @@ impl<'a> StructInfo<'a> { let (_, ty_generics, where_clause) = self.generics.split_for_impl(); let ref helper_trait_method_name = self.conversion_helper_method_name; - let assignments = self.fields.iter().map(|field| { - let ref name = field.name; - if let Some(ref default) = field.default { - quote!(#name: self.#name.#helper_trait_method_name(#default)) - } else { - quote!(#name: self.#name.0) + + match self.kind { + StructKind::Struct => { + let assignments = self.fields.iter().map(|field| { + let ref name = field.name(); + if let Some(ref default) = field.default { + quote!(#name: self.#name.#helper_trait_method_name(#default)) + } else { + quote!(#name: self.#name.0) + } + }); + + quote! { + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl #impl_generics #builder_name #modified_ty_generics #where_clause { + pub fn build(self) -> #name #ty_generics { + #name { + #( #assignments ),* + } + } + } + } } - }); + StructKind::Tuple => { + let assignments = self.fields.iter().map(|field| { + let ref name = field.name(); + if let Some(ref default) = field.default { + quote!(self.#name.#helper_trait_method_name(#default)) + } else { + quote!(self.#name.0) + } + }); - quote! { - #[allow(dead_code, non_camel_case_types, missing_docs)] - impl #impl_generics #builder_name #modified_ty_generics #where_clause { - pub fn build(self) -> #name #ty_generics { - #name { - #( #assignments ),* + quote! { + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl #impl_generics #builder_name #modified_ty_generics #where_clause { + pub fn build(self) -> #name #ty_generics { + #name ( + #( #assignments ),* + ) + } + } + } + } + StructKind::Unit => { + quote! { + #[allow(dead_code, non_camel_case_types, missing_docs)] + impl #impl_generics #builder_name #modified_ty_generics #where_clause { + pub fn build(self) -> #name #ty_generics { + #name + } } } }