commit 143b9c84de8cf9cf0e08f4007c5c380435827731 Author: Uttarayan Mondal Date: Fri Aug 11 11:42:42 2023 +0530 [init] Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..62b3910 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[workspace.package] +version = "0.1.0" +edition = "2021" +repository = "https://github.com/uttarayan21/comptime-builder" +documentation = "https://docs.rs/comptime-builder" + +[workspace] +members = [ + ".", + "comptime-builder-macros" +] + +[workspace.dependencies] +comptime-builder-macros = { version = "0.1.0", path = "comptime-builder-macros" } + +[package] +name = "comptime-builder" +version.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true + +[dependencies] +comptime-builder-macros.workspace = true diff --git a/comptime-builder-macros/Cargo.toml b/comptime-builder-macros/Cargo.toml new file mode 100644 index 0000000..e91760f --- /dev/null +++ b/comptime-builder-macros/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "comptime-builder-macros" +version.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true + +[dependencies] +convert_case = "0.6.0" +proc-macro2 = "1.0.66" +quote = "1.0.32" +syn = { version = "2.0.27", features = ["extra-traits", "full"] } + +[lib] +proc-macro = true diff --git a/comptime-builder-macros/src/lib.rs b/comptime-builder-macros/src/lib.rs new file mode 100644 index 0000000..c43dcbb --- /dev/null +++ b/comptime-builder-macros/src/lib.rs @@ -0,0 +1,149 @@ +use syn::*; + +#[proc_macro_derive(Builder)] +pub fn builder_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + dbg!(derive_builder::derive(input) + .unwrap_or_else(|err| err.into_compile_error()) + .into()) +} + +mod derive_builder { + use proc_macro2::*; + use quote::quote; + use syn::punctuated::Punctuated; + use syn::token::Comma; + use syn::*; + + pub fn derive(input: DeriveInput) -> Result { + let crate_name: syn::Path = parse_quote!(::comptime_builder); + let fields = match input.data { + Data::Struct(DataStruct { + fields: Fields::Named(FieldsNamed { named, .. }), + .. + }) => named, + _ => { + return Err(syn::Error::new_spanned( + input, + "expected a struct with named fields", + )) + } + }; + + let struct_name = &input.ident; + let builder_struct_name = Ident::new(&format!("{}Builder", struct_name), Span::call_site()); + + // Use Generic type T{0..} to represent the fields + let mut state = 1u32; + let generic_fields = fields.into_iter().scan(&mut state, |state, mut field| { + let gtype = format!("T{}", state); + **state += 1; + field.ty = syn::Type::Path(TypePath { + qself: None, + path: syn::Path { + leading_colon: None, + segments: Punctuated::from_iter(vec![PathSegment { + ident: syn::Ident::new(>ype, Span::call_site()), + arguments: PathArguments::None, + }]), + }, + }); + Some(field) + }); + let generic_fields: Punctuated = generic_fields.collect(); + let builder_generics = syn::Generics { + lt_token: Token![<](Span::call_site()).into(), + params: (1..state) + .map(|f| { + GenericParam::Type(TypeParam { + attrs: vec![], + ident: syn::Ident::new(&format!("T{}", f), Span::call_site()), + colon_token: None, + bounds: Punctuated::new(), + eq_token: None, + default: None, + }) + }) + .collect(), + gt_token: Token![>](Span::call_site()).into(), + where_clause: None, + }; + + let builder_struct = syn::ItemStruct { + attrs: vec![], + vis: Visibility::Inherited, + struct_token: Token![struct](Span::call_site()), + ident: builder_struct_name.clone(), + fields: Fields::Named(FieldsNamed { + brace_token: token::Brace::default(), + named: generic_fields, + }), + generics: builder_generics, + semi_token: None, + }; + + let empty_builder_type: syn::Type = syn::Type::Path(TypePath { + qself: None, + path: syn::Path { + leading_colon: None, + segments: Punctuated::from_iter(vec![PathSegment { + ident: builder_struct_name.clone(), + arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Token![<](Span::call_site()), + args: core::iter::repeat::(parse_quote!( + #crate_name::Empty + )) + .take(state as usize - 1) + .collect(), + gt_token: Token![>](Span::call_site()), + }), + }]), + }, + }); + + let empty_builder = syn::ExprStruct { + attrs: vec![], + qself: None, + path: parse_quote!(#builder_struct_name), + brace_token: token::Brace::default(), + fields: core::iter::repeat::(parse_quote!( + _field: #crate_name::Empty + )) + .take(state as usize - 1) + .collect(), + dot2_token: None, + rest: None, + }; + + let empty_builder_fn = syn::ImplItemFn { + attrs: vec![], + vis: Visibility::Inherited, + defaultness: None, + sig: parse_quote!(fn builder() -> #empty_builder_type), + block: parse_quote! {{ + #empty_builder + }}, + }; + + let ge = input.generics.params.clone(); + let self_ty = parse_quote!(#struct_name<#ge>); + let impl_empty_builder_fn = syn::ItemImpl { + attrs: vec![], + defaultness: None, + unsafety: None, + impl_token: Token![impl](Span::call_site()), + generics: input.generics.clone(), + trait_: None, + self_ty: Box::new(self_ty), + brace_token: token::Brace::default(), + items: vec![ImplItem::Fn(empty_builder_fn)], + }; + + Ok(quote! { + #impl_empty_builder_fn + #builder_struct + }) + } +} diff --git a/examples/date.rs b/examples/date.rs new file mode 100644 index 0000000..3e219a3 --- /dev/null +++ b/examples/date.rs @@ -0,0 +1,101 @@ +use comptime_builder::*; + +impl WithField<1, u16, Date> for DateBuilder { + type Output = DateBuilder, M, D>; + fn with_field(self, value: u16) -> Self::Output { + DateBuilder { + year: Field(value), + month: self.month, + day: self.day, + } + } +} + +impl WithField<2, u8, Date> for DateBuilder { + type Output = DateBuilder, D>; + fn with_field(self, value: u8) -> Self::Output { + DateBuilder { + year: self.year, + month: Field(value), + day: self.day, + } + } +} + +impl WithField<3, u8, Date> for DateBuilder { + type Output = DateBuilder>; + fn with_field(self, value: u8) -> Self::Output { + DateBuilder { + year: self.year, + month: self.month, + day: Field(value), + } + } +} + +pub trait WithYear: WithField<1, u16, Date> { + fn with_year(self, value: u16) -> Self::Output { + self.with_field(value) + } +} + +impl WithYear for T where T: WithField<1, u16, Date> {} + +pub trait WithMonth: WithField<2, u8, Date> { + fn with_month(self, value: u8) -> Self::Output { + self.with_field(value) + } +} + +impl WithMonth for T where T: WithField<2, u8, Date> {} + +pub trait WithDay: WithField<3, u8, Date> { + fn with_day(self, value: u8) -> Self::Output { + self.with_field(value) + } +} + +impl WithDay for T where T: WithField<3, u8, Date> {} + +pub struct DateBuilder { + year: Year, + month: Month, + day: Day, +} + +impl DateBuilder, Field<2, u8>, Field<3, u8>> { + fn build(self) -> Date { + Date { + year: self.year.0, + month: self.month.0, + day: self.day.0, + } + } +} + +#[derive(Debug)] +pub struct Date { + year: u16, + month: u8, + day: u8, +} +pub struct Empty; + +impl Date { + pub fn builder() -> DateBuilder { + DateBuilder { + year: Empty, + month: Empty, + day: Empty, + } + } +} + +pub fn main() { + let date = Date::builder() + .with_year(2022) + .with_month(5) + .with_day(1) + .build(); + dbg!(date); +} diff --git a/examples/derive.rs b/examples/derive.rs new file mode 100644 index 0000000..7d5b9be --- /dev/null +++ b/examples/derive.rs @@ -0,0 +1,12 @@ +use comptime_builder::Builder; + +#[derive(Builder)] +pub struct MyStruct { + pub my_field: T1, + pub my_other_field: T2, + pub our_field: String, +} + +pub fn main() { + +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d1ee22c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,13 @@ +pub use comptime_builder_macros::Builder; + +pub struct Empty; + + +pub trait HasField {} +impl HasField for Field {} +pub struct Field(pub T); + +pub trait WithField: Sized { + type Output; + fn with_field(self, value: Field) -> Self::Output; +}