From f6e078d9624090aa65cd13085885683b4e94e196 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Thu, 14 Sep 2023 10:13:10 +0200 Subject: [PATCH 01/65] Added documentation to most of the code (#9) * Improve examples * Add loads of docs * Add documentation to generators and symbol * run fmt --- .../src/generator/builder_generator.rs | 27 +++++ .../src/generator/data_generator.rs | 21 ++++ .../src/generator/field_generator.rs | 59 +++++++--- .../src/generator/generics_generator.rs | 62 +++++++++- .../src/generator/group_generator.rs | 26 ++++- .../src/generator/mod.rs | 15 +++ .../src/generator/target_generator.rs | 27 ++++- .../src/info/field_info.rs | 108 ++++++++++++++++++ .../src/info/group_info.rs | 30 +++++ .../src/info/struct_info.rs | 70 +++++++++--- const_typed_builder_derive/src/lib.rs | 36 +++++- const_typed_builder_derive/src/symbol.rs | 11 ++ src/lib.rs | 18 ++- 13 files changed, 461 insertions(+), 49 deletions(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 3543d33..2fe0d93 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -6,6 +6,8 @@ use crate::{StreamResult, VecStreamResult}; use proc_macro2::TokenStream; use quote::quote; +// The `BuilderGenerator` struct is responsible for generating code related to the builder struct, +/// including its definition, implementation of setter methods, `new` method, and `build` method. pub(super) struct BuilderGenerator<'a> { group_gen: GroupGenerator<'a>, field_gen: FieldGenerator<'a>, @@ -17,6 +19,21 @@ pub(super) struct BuilderGenerator<'a> { } impl<'a> BuilderGenerator<'a> { + /// Creates a new `BuilderGenerator` instance for code generation. + /// + /// # Arguments + /// + /// - `group_gen`: The `GroupGenerator` responsible for generating group-related code. + /// - `field_gen`: The `FieldGenerator` responsible for generating field-related code. + /// - `generics_gen`: The `GenericsGenerator` responsible for generating generics information. + /// - `target_name`: A reference to the identifier representing the target struct's name. + /// - `target_vis`: A reference to the visibility of the target struct. + /// - `builder_name`: A reference to the identifier representing the builder struct's name. + /// - `data_name`: A reference to the identifier representing the data struct's name. + /// + /// # Returns + /// + /// A `BuilderGenerator` instance initialized with the provided information. pub fn new( group_gen: GroupGenerator<'a>, field_gen: FieldGenerator<'a>, @@ -37,6 +54,11 @@ impl<'a> BuilderGenerator<'a> { } } + // Generates the code for the builder struct and its methods and returns a token stream. + /// + /// # Returns + /// + /// A `StreamResult` representing the generated code for the builder struct and methods. pub fn generate(&self) -> StreamResult { let builder_struct = self.generate_struct(); let builder_impl = self.generate_impl()?; @@ -47,6 +69,7 @@ impl<'a> BuilderGenerator<'a> { Ok(tokens) } + /// Generates the code for the builder struct definition. fn generate_struct(&self) -> TokenStream { let data_name = self.data_name; let builder_name = self.builder_name; @@ -66,6 +89,7 @@ impl<'a> BuilderGenerator<'a> { ) } + /// Generates the implementation code for the builder struct's `new`, `build` and setter methods. fn generate_impl(&self) -> StreamResult { let builder_setters = self.generate_setters_impl()?; let builder_new = self.generate_new_impl(); @@ -79,6 +103,7 @@ impl<'a> BuilderGenerator<'a> { Ok(tokens) } + /// Generates the code for the `new` method implementation. fn generate_new_impl(&self) -> TokenStream { let builder_name = self.builder_name; let data_name = self.data_name; @@ -103,6 +128,7 @@ impl<'a> BuilderGenerator<'a> { ) } + /// Generates the code for the `build` method implementation. fn generate_build_impl(&self) -> TokenStream { let builder_name = self.builder_name; let impl_generics = self @@ -131,6 +157,7 @@ impl<'a> BuilderGenerator<'a> { ) } + /// Generates the code for the setter methods of the builder. fn generate_setters_impl(&self) -> StreamResult { let builder_name = self.builder_name; let setters = self diff --git a/const_typed_builder_derive/src/generator/data_generator.rs b/const_typed_builder_derive/src/generator/data_generator.rs index c971b22..94ed40d 100644 --- a/const_typed_builder_derive/src/generator/data_generator.rs +++ b/const_typed_builder_derive/src/generator/data_generator.rs @@ -2,6 +2,8 @@ use super::{field_generator::FieldGenerator, generics_generator::GenericsGenerat use crate::StreamResult; use quote::quote; +/// The `DataGenerator` struct is responsible for generating code related to the data struct +/// that corresponds to the target struct and the conversion implementations. pub(super) struct DataGenerator<'a> { field_gen: FieldGenerator<'a>, generics_gen: GenericsGenerator<'a>, @@ -10,6 +12,18 @@ pub(super) struct DataGenerator<'a> { } impl<'a> DataGenerator<'a> { + /// Creates a new `DataGenerator` instance for code generation. + /// + /// # Arguments + /// + /// - `field_gen`: The `FieldGenerator` responsible for generating field-related code. + /// - `generics_gen`: The `GenericsGenerator` responsible for generating generics information. + /// - `target_name`: A reference to the identifier representing the target struct's name. + /// - `data_name`: A reference to the identifier representing the data struct's name. + /// + /// # Returns + /// + /// A `DataGenerator` instance initialized with the provided information. pub(super) fn new( field_gen: FieldGenerator<'a>, generics_gen: GenericsGenerator<'a>, @@ -24,6 +38,11 @@ impl<'a> DataGenerator<'a> { } } + /// Generates the code for the data struct and the conversion implementations and returns a token stream. + /// + /// # Returns + /// + /// A `StreamResult` representing the generated code for the data struct and conversions. pub fn generate(&self) -> StreamResult { let data_struct = self.generate_struct()?; let data_impl = self.generate_impl()?; @@ -36,6 +55,7 @@ impl<'a> DataGenerator<'a> { Ok(tokens) } + /// Generates the implementation code for conversions between the data struct and the target struct. fn generate_impl(&self) -> StreamResult { let data_name = self.data_name; let struct_name = self.target_name; @@ -65,6 +85,7 @@ impl<'a> DataGenerator<'a> { Ok(tokens) } + /// Generates the code for the data struct itself. fn generate_struct(&self) -> StreamResult { let data_name = self.data_name; diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index e026bf9..a09925f 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -2,20 +2,36 @@ use crate::{info::FieldInfo, VecStreamResult}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +/// The `FieldGenerator` struct is responsible for generating code related to fields of the target and data structs. #[derive(Debug, Clone)] pub(super) struct FieldGenerator<'a> { fields: &'a [FieldInfo<'a>], } impl<'a> FieldGenerator<'a> { + /// Creates a new `FieldGenerator` instance. + /// + /// # Arguments + /// + /// - `fields`: A reference to a slice of `FieldInfo` representing the fields of the struct. + /// + /// # Returns + /// + /// A `FieldGenerator` instance initialized with the provided fields. pub fn new(fields: &'a [FieldInfo]) -> Self { Self { fields } } + /// Returns a reference to the fields of the struct. pub fn fields(&self) -> &[FieldInfo] { self.fields } + /// Generates code for the fields of the data struct and returns a token stream. + /// + /// # Returns + /// + /// A `VecStreamResult` representing the generated code for the data struct fields. pub fn data_struct_fields(&self) -> VecStreamResult { self.fields .iter() @@ -42,6 +58,11 @@ impl<'a> FieldGenerator<'a> { .collect() } + // Generates code for the `From` trait implementation for converting data struct fields to target struct fields and returns a token stream. + /// + /// # Returns + /// + /// A `VecStreamResult` representing the generated code for the `From` trait implementation. pub fn data_impl_from_fields(&self) -> VecStreamResult { self.fields .iter() @@ -63,6 +84,11 @@ impl<'a> FieldGenerator<'a> { .collect() } + /// Generates default field values for the data struct and returns a token stream. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated default field values. pub fn data_impl_default_fields(&self) -> TokenStream { let fields_none = self.fields.iter().map(|field| { let field_name = field.ident(); @@ -73,20 +99,18 @@ impl<'a> FieldGenerator<'a> { ) } - fn field_effective_type(field: &'a FieldInfo) -> &'a syn::Type { - match field { - FieldInfo::Optional(field) => field.ty(), - FieldInfo::Mandatory(field) if field.is_option_type() => field - .inner_type() - .expect("Couldn't read inner type of option, even though it's marked as optional"), - FieldInfo::Mandatory(field) => field.ty(), - FieldInfo::Grouped(field) => field.inner_type(), - } - } - + /// Generates code for the input type of a builder setter method and returns a token stream. + /// + /// # Arguments + /// + /// - `field`: A reference to the `FieldInfo` for which the setter input type is generated. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated input type for the builder setter method. pub fn builder_set_impl_input_type(&self, field: &'a FieldInfo) -> TokenStream { let field_name = field.ident(); - let field_ty = Self::field_effective_type(field); + let field_ty = field.setter_input_type(); let bottom_ty = if field.is_option_type() { field.inner_type().unwrap() } else { @@ -102,10 +126,19 @@ impl<'a> FieldGenerator<'a> { quote!(#field_name: #field_ty) } + /// Generates code for the input value of a builder setter method and returns a token stream. + /// + /// # Arguments + /// + /// - `field`: A reference to the `FieldInfo` for which the setter input value is generated. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated input value for the builder setter method. pub fn builder_set_impl_input_value(&self, field: &'a FieldInfo) -> TokenStream { let field_name = field.ident(); - let field_ty = Self::field_effective_type(field); + let field_ty = field.setter_input_type(); let bottom_ty = if field.is_option_type() { field.inner_type().unwrap() } else { diff --git a/const_typed_builder_derive/src/generator/generics_generator.rs b/const_typed_builder_derive/src/generator/generics_generator.rs index 933eb74..e99d4e3 100644 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ b/const_typed_builder_derive/src/generator/generics_generator.rs @@ -4,12 +4,23 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::parse_quote; +/// The `GenericsGenerator` struct is responsible for generating code related to generics in the target struct, builder, and data types. #[derive(Debug, Clone)] pub(super) struct GenericsGenerator<'a> { pub fields: &'a [FieldInfo<'a>], target_generics: &'a syn::Generics, } +/// Creates a new `GenericsGenerator` instance. +/// +/// # Arguments +/// +/// - `fields`: A reference to a slice of `FieldInfo` representing the fields of the struct. +/// - `target_generics`: A reference to the generics of the target struct. +/// +/// # Returns +/// +/// A `GenericsGenerator` instance initialized with the provided fields and target generics. impl<'a> GenericsGenerator<'a> { pub fn new(fields: &'a [FieldInfo], target_generics: &'a syn::Generics) -> Self { Self { @@ -18,10 +29,20 @@ impl<'a> GenericsGenerator<'a> { } } + /// Returns a reference to the target generics of the struct. pub fn target_generics(&self) -> &syn::Generics { self.target_generics } + /// Generates const generics with boolean values and returns a token stream. + /// + /// # Arguments + /// + /// - `value`: A boolean value to set for the const generics. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated const generics. pub fn const_generics_valued(&self, value: bool) -> TokenStream { let mut all = self.fields.iter().flat_map(|field| match field { FieldInfo::Optional(_) => Box::new(std::iter::empty()) @@ -41,6 +62,16 @@ impl<'a> GenericsGenerator<'a> { self.add_const_generics_valued_for_type(&mut all) } + /// Generates type const generics for the input type of a builder setter method and returns a token stream. + /// + /// # Arguments + /// + /// - `field_info`: A reference to the `FieldInfo` for which the const generics are generated. + /// - `value`: A boolean value to set for the const generics. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated const generics for the setter method input type. pub fn builder_const_generic_idents_set_type( &self, field_info: &FieldInfo, @@ -76,6 +107,15 @@ impl<'a> GenericsGenerator<'a> { self.add_const_generics_valued_for_type(&mut all) } + /// Generates impl const generics for the input type of a builder setter method and returns a token stream. + /// + /// # Arguments + /// + /// - `field_info`: A reference to the `FieldInfo` for which the const generics are generated. + /// + /// # Returns + /// + /// A `syn::Generics` instance representing the generated const generics for the setter method input type. pub fn builder_const_generic_idents_set_impl(&self, field_info: &FieldInfo) -> syn::Generics { let mut all = self.fields.iter().flat_map(|field| match field { FieldInfo::Optional(_) => { @@ -99,13 +139,19 @@ impl<'a> GenericsGenerator<'a> { self.add_const_generics_for_impl(&mut all) } + // Generates const generics for the builder `build` method and returns a token stream. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated const generics for the builder `build` method. pub fn builder_const_generic_idents_build(&self) -> TokenStream { let mut all = self.fields.iter().flat_map(|field| match field { FieldInfo::Optional(_) => Box::new(std::iter::empty()) as Box>>, - FieldInfo::Mandatory(_) => Box::new(std::iter::once( - Either::Right(syn::LitBool::new(true, proc_macro2::Span::call_site())), // FIXME - )) + FieldInfo::Mandatory(_) => Box::new(std::iter::once(Either::Right(syn::LitBool::new( + true, + proc_macro2::Span::call_site(), + )))) as Box>>, FieldInfo::Grouped(grouped) => Box::new( grouped @@ -118,6 +164,11 @@ impl<'a> GenericsGenerator<'a> { self.add_const_generics_valued_for_type(&mut all) } + /// Generates const generics for builder group partial identifiers and returns a `syn::Generics` instance. + /// + /// # Returns + /// + /// A `syn::Generics` instance representing the generated const generics for builder group partial identifiers. pub fn builder_const_generic_group_partial_idents(&self) -> syn::Generics { let mut all = self.fields.iter().flat_map(|field| match field { FieldInfo::Optional(_) => { @@ -136,6 +187,11 @@ impl<'a> GenericsGenerator<'a> { self.add_const_generics_for_impl(&mut all) } + /// Generates const generics for the builder struct and returns a `syn::Generics` instance. + /// + /// # Returns + /// + /// A `syn::Generics` instance representing the generated const generics for the builder struct. pub fn builder_struct_generics(&self) -> syn::Generics { let mut all = self.fields.iter().flat_map(|field| match field { FieldInfo::Optional(_) => { diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index 70bf289..c88d159 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -2,16 +2,31 @@ use crate::info::{GroupInfo, GroupType}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +/// The `GroupGenerator` struct is responsible for generating code related to groups within the builder, including correctness checks and verifications. #[derive(Debug)] pub(super) struct GroupGenerator<'a> { groups: Vec<&'a GroupInfo>, } impl<'a> GroupGenerator<'a> { + /// Creates a new `GroupGenerator` instance. + /// + /// # Arguments + /// + /// - `groups`: A vector of references to `GroupInfo` representing the groups associated with the builder. + /// + /// # Returns + /// + /// A `GroupGenerator` instance initialized with the provided groups. pub fn new(groups: Vec<&'a GroupInfo>) -> Self { Self { groups } } + /// Generates correctness helper functions for group validation and returns a `TokenStream`. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated correctness helper functions. pub fn builder_build_impl_correctness_helper_fns(&self) -> TokenStream { if self.groups.is_empty() { return TokenStream::new(); @@ -86,11 +101,20 @@ impl<'a> GroupGenerator<'a> { #at_most ) } - + /// Generates the correctness check for groups and returns a `TokenStream` as an optional value. + /// + /// # Returns + /// + /// An optional `TokenStream` representing the generated correctness check. Returns `None` if there are no groups. pub fn builder_build_impl_correctness_check(&self) -> Option { (!self.groups.is_empty()).then(|| quote!(let _ = Self::GROUP_VERIFIER;)) } + /// Generates the correctness verifier for groups and returns an optional `TokenStream`. + /// + /// # Returns + /// + /// An optional `TokenStream` representing the generated correctness verifier. Returns `None` if there are no groups. pub fn builder_build_impl_correctness_verifier(&self) -> Option { if self.groups.is_empty() { return None; diff --git a/const_typed_builder_derive/src/generator/mod.rs b/const_typed_builder_derive/src/generator/mod.rs index 8e1116b..52ce890 100644 --- a/const_typed_builder_derive/src/generator/mod.rs +++ b/const_typed_builder_derive/src/generator/mod.rs @@ -13,6 +13,7 @@ use self::{ use crate::{info::StructInfo, StreamResult}; use quote::quote; +/// The `Generator` struct is responsible for generating code for the builder pattern based on the provided `StructInfo`. pub struct Generator<'a> { data_gen: DataGenerator<'a>, target_gen: TargetGenerator<'a>, @@ -20,6 +21,15 @@ pub struct Generator<'a> { } impl<'a> Generator<'a> { + /// Creates a new `Generator` instance for code generation. + /// + /// # Arguments + /// + /// - `info`: A reference to the `StructInfo` representing the input struct. + /// + /// # Returns + /// + /// A `Generator` instance initialized with the provided `StructInfo`. pub fn new(info: &'a StructInfo<'a>) -> Self { let generics_gen = GenericsGenerator::new(info.field_infos(), info.generics()); let field_gen = FieldGenerator::new(info.field_infos()); @@ -48,6 +58,11 @@ impl<'a> Generator<'a> { } } + /// Generates the builder pattern code and returns a token stream. + /// + /// # Returns + /// + /// A `StreamResult` representing the generated token stream. pub fn generate(&self) -> StreamResult { let target = self.target_gen.generate(); let data = self.data_gen.generate()?; diff --git a/const_typed_builder_derive/src/generator/target_generator.rs b/const_typed_builder_derive/src/generator/target_generator.rs index 807a994..be71d25 100644 --- a/const_typed_builder_derive/src/generator/target_generator.rs +++ b/const_typed_builder_derive/src/generator/target_generator.rs @@ -1,9 +1,9 @@ +use super::generics_generator::GenericsGenerator; use proc_macro2::TokenStream; - use quote::quote; -use super::generics_generator::GenericsGenerator; - +/// The `TargetGenerator` struct is responsible for generating code for the target struct implementation +/// of the builder pattern based on the provided `GenericsGenerator`, target name, and builder name. pub(super) struct TargetGenerator<'a> { generics_gen: GenericsGenerator<'a>, target_name: &'a syn::Ident, @@ -11,6 +11,17 @@ pub(super) struct TargetGenerator<'a> { } impl<'a> TargetGenerator<'a> { + /// Creates a new `TargetGenerator` instance for code generation. + /// + /// # Arguments + /// + /// - `generics_gen`: The `GenericsGenerator` responsible for generating generics information. + /// - `target_name`: A reference to the identifier representing the target struct's name. + /// - `builder_name`: A reference to the identifier representing the builder struct's name. + /// + /// # Returns + /// + /// A `TargetGenerator` instance initialized with the provided information. pub fn new( generics_gen: GenericsGenerator<'a>, target_name: &'a syn::Ident, @@ -23,10 +34,16 @@ impl<'a> TargetGenerator<'a> { } } + /// Generates the target struct's builder implementation code and returns a token stream. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated code for the builder implementation. pub fn generate(&self) -> TokenStream { self.generate_impl() } + /// Generates the actual implementation code for the target struct. fn generate_impl(&self) -> TokenStream { let target_name = self.target_name; let builder_name = self.builder_name; @@ -39,8 +56,8 @@ impl<'a> TargetGenerator<'a> { impl #impl_generics Builder for #target_name #type_generics #where_clause { type BuilderImpl = #builder_name #const_generics; - fn builder() -> Self:: BuilderImpl { - Self:: BuilderImpl::new() + fn builder() -> Self::BuilderImpl { + Self::BuilderImpl::new() } } } diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index b78e10e..770aa07 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -9,14 +9,28 @@ use crate::{ util::{inner_type, is_option}, }; +/// Represents the information about a struct field used for code generation. #[derive(Debug, PartialEq, Eq)] pub enum FieldInfo<'a> { + /// Represents an optional field. Optional(FieldInfoOptional<'a>), + /// Represents a mandatory field. Mandatory(FieldInfoMandatory<'a>), + /// Represents a grouped field. Grouped(FieldInfoGrouped<'a>), } impl<'a> FieldInfo<'a> { + /// Creates a new `FieldInfo` instance from a `syn::Field` and `StructSettings`. + /// + /// # Arguments + /// + /// - `field`: A `syn::Field` representing the input field. + /// - `struct_settings`: A mutable reference to `StructSettings`. + /// + /// # Returns + /// + /// A `syn::Result` containing the `FieldInfo` instance if successful, or an error if parsing fails. pub fn new(field: &'a syn::Field, struct_settings: &mut StructSettings) -> syn::Result { if let syn::Field { attrs, @@ -72,6 +86,7 @@ impl<'a> FieldInfo<'a> { } } + /// Retrieves the identifier of the field. pub fn ident(&self) -> &syn::Ident { match self { FieldInfo::Optional(field) => field.ident(), @@ -80,6 +95,7 @@ impl<'a> FieldInfo<'a> { } } + /// Retrieves whether the field's attributes indicate builder propagation. pub fn propagate(&self) -> bool { match self { FieldInfo::Optional(field) => field.propagate(), @@ -88,6 +104,7 @@ impl<'a> FieldInfo<'a> { } } + /// Checks if the field's type is an `Option`. pub fn is_option_type(&self) -> bool { match self { FieldInfo::Optional(_) => true, @@ -96,6 +113,7 @@ impl<'a> FieldInfo<'a> { } } + /// Retrieves the inner type of the field if it is an `Option`. pub fn inner_type(&self) -> Option<&syn::Type> { match self { FieldInfo::Optional(field) => Some(field.inner_type()), @@ -103,8 +121,21 @@ impl<'a> FieldInfo<'a> { FieldInfo::Grouped(field) => Some(field.inner_type()), } } + + /// Retrieves the input type for the builder's setter method. + pub fn setter_input_type(&self) -> &syn::Type { + match self { + FieldInfo::Optional(field) => field.ty(), + FieldInfo::Mandatory(field) if field.is_option_type() => field + .inner_type() + .expect("Couldn't read inner type of option, even though it's marked as optional"), + FieldInfo::Mandatory(field) => field.ty(), + FieldInfo::Grouped(field) => field.inner_type(), + } + } } +/// Represents information about an optional field. #[derive(Debug, PartialEq, Eq)] pub struct FieldInfoOptional<'a> { field: &'a syn::Field, @@ -114,6 +145,18 @@ pub struct FieldInfoOptional<'a> { } impl<'a> FieldInfoOptional<'a> { + /// Creates a new `FieldInfoOptional` instance from a `syn::Field`. + /// + /// # Arguments + /// + /// - `field`: A `syn::Field` representing the input field. + /// - `ident`: A reference to the identifier of the field. + /// - `propagate`: A boolean indicating whether the field should propagate values. + /// + /// # Returns + /// + /// A `syn::Result` containing the `FieldInfoOptional` instance if successful, or an error if parsing fails. + fn new(field: &'a syn::Field, ident: &'a syn::Ident, propagate: bool) -> syn::Result { Ok(Self { field, @@ -124,23 +167,28 @@ impl<'a> FieldInfoOptional<'a> { }) } + /// Retrieves the type of the field. pub fn ty(&self) -> &syn::Type { &self.field.ty } + /// Retrieves the inner type of the field. fn inner_type(&self) -> &syn::Type { self.inner_ty } + /// Retrieves the identifier of the field. pub fn ident(&self) -> &syn::Ident { self.ident } + /// Checks if the field's attributes indicate builder propagation. pub fn propagate(&self) -> bool { self.propagate } } +/// Represents information about a mandatory field. #[derive(Debug, PartialEq, Eq)] pub struct FieldInfoMandatory<'a> { field: &'a syn::Field, @@ -151,6 +199,18 @@ pub struct FieldInfoMandatory<'a> { } impl<'a> FieldInfoMandatory<'a> { + /// Creates a new `FieldInfoMandatory` instance from a `syn::Field`. + /// + /// # Arguments + /// + /// - `field`: A `syn::Field` representing the input field. + /// - `ident`: A reference to the identifier of the field. + /// - `propagate`: A boolean indicating whether the field should propagate values. + /// - `mandatory_index`: The index of the mandatory field. + /// + /// # Returns + /// + /// A `syn::Result` containing the `FieldInfoMandatory` instance if successful, or an error if parsing fails. fn new( field: &'a syn::Field, ident: &'a syn::Ident, @@ -166,31 +226,38 @@ impl<'a> FieldInfoMandatory<'a> { }) } + /// Retrieves the type of the field. pub fn ty(&self) -> &syn::Type { &self.field.ty } + /// Retrieves the inner type of the field. pub fn inner_type(&self) -> Option<&syn::Type> { self.inner_ty } + /// Retrieves the identifier of the field. pub fn ident(&self) -> &syn::Ident { self.ident } + /// Checks if the field's attributes indicate propagation. pub fn propagate(&self) -> bool { self.propagate } + /// Retrieves the index of the mandatory field. pub fn mandatory_index(&self) -> usize { self.mandatory_index } + /// Checks if the field's type is an `Option`. pub fn is_option_type(&self) -> bool { is_option(&self.field.ty) } } +/// Represents information about a grouped field. #[derive(Debug, PartialEq, Eq)] pub struct FieldInfoGrouped<'a> { field: &'a syn::Field, @@ -201,6 +268,18 @@ pub struct FieldInfoGrouped<'a> { } impl<'a> FieldInfoGrouped<'a> { + /// Creates a new `FieldInfoGrouped` instance from a `syn::Field`. + /// + /// # Arguments + /// + /// - `field`: A `syn::Field` representing the input field. + /// - `ident`: A reference to the identifier of the field. + /// - `propagate`: A boolean indicating whether the field should propagate values. + /// - `group_indices`: A map of `GroupInfo` to group indices. + /// + /// # Returns + /// + /// A `syn::Result` containing the `FieldInfoGrouped` instance if successful, or an error if parsing fails. fn new( field: &'a syn::Field, ident: &'a syn::Ident, @@ -217,32 +296,42 @@ impl<'a> FieldInfoGrouped<'a> { }) } + /// Retrieves the type of the field. pub fn ty(&self) -> &syn::Type { &self.field.ty } + /// Retrieves the inner type of the field. pub fn inner_type(&self) -> &syn::Type { self.inner_ty } + /// Retrieves the identifier of the field. pub fn ident(&self) -> &syn::Ident { self.ident } + /// Checks if the field's attributes indicate propagation. pub fn propagate(&self) -> bool { self.propagate } + /// Retrieves the group indices associated with the field. pub fn group_indices(&self) -> &HashMap { &self.group_indices } } +/// Represents settings for struct field generation. #[derive(Debug, Clone)] pub struct FieldSettings { + /// Indicates if the field is mandatory. pub mandatory: bool, + /// Indicates if the field should propagate values. pub propagate: bool, + /// The input name for the builder's setter method. pub input_name: syn::Ident, + /// A set of group names associated with the field. pub groups: HashSet, } @@ -258,10 +347,20 @@ impl Default for FieldSettings { } impl FieldSettings { + /// Creates a new `FieldSettings` instance with default values. pub fn new() -> FieldSettings { Self::default() } + /// Updates field settings based on provided attributes. + /// + /// # Arguments + /// + /// - `attrs`: A slice of `syn::Attribute` representing the attributes applied to the field. + /// + /// # Returns + /// + /// A `syn::Result` indicating success or failure of attribute handling. pub fn with_attrs(mut self, attrs: &[syn::Attribute]) -> syn::Result { attrs .iter() @@ -270,6 +369,15 @@ impl FieldSettings { Ok(self) } + /// Updates field settings based on the field's type. + /// + /// # Arguments + /// + /// - `ty`: A reference to the `syn::Type` representing the field's type. + /// + /// # Returns + /// + /// The updated `FieldSettings` instance. pub fn with_ty(mut self, ty: &syn::Type) -> Self { if !self.mandatory && !is_option(ty) { self.mandatory = true; diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group_info.rs index d6036c7..8e36a55 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group_info.rs @@ -4,6 +4,7 @@ use quote::format_ident; use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; +/// Represents information about a group, including its name, member count, and group type. #[derive(Debug, Clone)] pub struct GroupInfo { name: syn::Ident, @@ -12,6 +13,16 @@ pub struct GroupInfo { } impl GroupInfo { + /// Creates a new `GroupInfo` instance. + /// + /// # Arguments + /// + /// - `name`: The identifier representing the group's name. + /// - `group_type`: The type of the group. + /// + /// # Returns + /// + /// A `GroupInfo` instance with the provided name and group type. pub fn new(name: syn::Ident, group_type: GroupType) -> Self { GroupInfo { name, @@ -20,10 +31,12 @@ impl GroupInfo { } } + /// Retrieves the name of the group. pub fn name(&self) -> &syn::Ident { &self.name } + /// Retrieves the expected member count based on the group type. pub fn expected_count(&self) -> usize { match self.group_type { GroupType::Exact(expected) => expected, @@ -32,19 +45,31 @@ impl GroupInfo { } } + /// Retrieves the current member count of the group. pub fn member_count(&self) -> usize { self.member_count } + /// Increments the member count and returns the next available index. pub fn next_index(&mut self) -> usize { self.member_count += 1; self.member_count - 1 } + /// Generates a partial constant identifier for the group at the given index. + /// + /// # Arguments + /// + /// - `index`: The index for which to generate the partial constant identifier. + /// + /// # Returns + /// + /// A `syn::Ident` representing the partial constant identifier. pub fn partial_const_ident(&self, index: usize) -> syn::Ident { format_ident!("{}_{}", &self.name.to_string().to_ascii_uppercase(), index) } + /// Retrieves the function symbol associated with the group type. pub fn function_symbol(&self) -> Symbol { match self.group_type { GroupType::Exact(_) => EXACT, @@ -53,6 +78,7 @@ impl GroupInfo { } } + /// Retrieves the group type. pub fn group_type(&self) -> &GroupType { &self.group_type } @@ -72,9 +98,13 @@ impl Hash for GroupInfo { } } +/// Represents the type of a group, which can be one of three variants: Exact, AtLeast, or AtMost. #[derive(Debug, Clone)] pub enum GroupType { + /// Represents a group with an exact member count. Exact(usize), + /// Represents a group with at least a certain number of members. AtLeast(usize), + /// Represents a group with at most a certain number of members. AtMost(usize), } diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index ae846ba..211c21e 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -7,20 +7,38 @@ use syn::Token; use crate::symbol::{ASSUME_MANDATORY, AT_LEAST, AT_MOST, BUILDER, EXACT, GROUP, SINGLE}; +/// A type alias for a collection of `FieldInfo` instances. type FieldInfos<'a> = Vec>; +/// Represents the information about a struct used for code generation. #[derive(Debug)] pub struct StructInfo<'a> { + /// The identifier of the struct. ident: &'a syn::Ident, + /// The visibility of the struct. vis: &'a syn::Visibility, + /// The generics of the struct. generics: &'a syn::Generics, + /// The identifier of the generated builder struct. builder_ident: syn::Ident, + /// The identifier of the generated data struct. data_ident: syn::Ident, + /// A map of group names to their respective `GroupInfo`. groups: HashMap, + /// A collection of `FieldInfo` instances representing struct fields. field_infos: FieldInfos<'a>, } impl<'a> StructInfo<'a> { + /// Creates a new `StructInfo` instance from a `syn::DeriveInput`. + /// + /// # Arguments + /// + /// - `ast`: A `syn::DeriveInput` representing the input struct. + /// + /// # Returns + /// + /// A `syn::Result` containing the `StructInfo` instance if successful, pub fn new(ast: &'a syn::DeriveInput) -> syn::Result { if let syn::DeriveInput { attrs, @@ -34,8 +52,17 @@ impl<'a> StructInfo<'a> { }), } = &ast { + if fields.named.is_empty() { + return Err(syn::Error::new_spanned(fields, "No fields found")); + } + let mut settings = StructSettings::new().with_attrs(attrs)?; - let field_infos = Self::parse_fields(&mut settings, fields)?; + + let field_infos = fields + .named + .iter() + .map(|field| FieldInfo::new(field, &mut settings)) + .collect::>>()?; let info = StructInfo { ident, @@ -55,55 +82,54 @@ impl<'a> StructInfo<'a> { } } - fn parse_fields( - settings: &mut StructSettings, - fields: &'a syn::FieldsNamed, - ) -> syn::Result> { - if fields.named.is_empty() { - return Err(syn::Error::new_spanned(fields, "No fields found")); - } - fields - .named - .iter() - .map(|field| FieldInfo::new(field, settings)) - .collect::>>() - } - + /// Retrieves the identifier of the struct. pub fn name(&self) -> &syn::Ident { self.ident } + /// Retrieves the visibility of the struct. pub fn vis(&self) -> &syn::Visibility { self.vis } + /// Retrieves the generics of the struct. pub fn generics(&self) -> &syn::Generics { self.generics } + /// Retrieves the identifier of the generated builder struct. pub fn builder_name(&self) -> &syn::Ident { &self.builder_ident } + /// Retrieves the identifier of the generated data struct. pub fn data_name(&self) -> &syn::Ident { &self.data_ident } + /// Retrieves a reference to the collection of `FieldInfo` instances representing struct fields. pub fn field_infos(&self) -> &FieldInfos { &self.field_infos } + /// Retrieves a reference to the map of group names to their respective `GroupInfo`. pub fn groups(&self) -> &HashMap { &self.groups } } +/// Represents settings for struct generation. #[derive(Debug)] pub struct StructSettings { + /// The suffix to be added to the generated builder struct name. builder_suffix: String, + /// The suffix to be added to the generated data struct name. data_suffix: String, + /// Default field settings. default_field_settings: FieldSettings, + /// A map of group names to their respective `GroupInfo`. groups: HashMap, + /// The count of mandatory fields encountered. mandatory_count: usize, } @@ -120,28 +146,42 @@ impl Default for StructSettings { } impl StructSettings { + /// Creates a new `StructSettings` instance with default values. fn new() -> Self { Default::default() } + /// Retrieves the next available mandatory field index. pub fn next_mandatory(&mut self) -> usize { self.mandatory_count += 1; self.mandatory_count - 1 } + /// Retrieves the next available group index for a given group name. pub fn next_group_index(&mut self, group_name: &String) -> Option { let res = self.groups.get_mut(group_name)?.next_index(); Some(res) } + /// Retrieves a reference to a `GroupInfo` instance by group name. pub fn group_by_name(&self, group_name: &String) -> Option<&GroupInfo> { self.groups.get(group_name) } + /// Retrieves the default field settings. pub fn default_field_settings(&self) -> &FieldSettings { &self.default_field_settings } + /// Updates struct settings based on provided attributes. + /// + /// # Arguments + /// + /// - `attrs`: A slice of `syn::Attribute` representing the attributes applied to the struct. + /// + /// # Returns + /// + /// A `syn::Result` indicating success or failure of attribute handling. pub fn with_attrs(mut self, attrs: &[syn::Attribute]) -> syn::Result { attrs .iter() diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index c34f3d2..779eb86 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -8,11 +8,31 @@ use info::StructInfo; use proc_macro2::TokenStream; use syn::DeriveInput; +/// A type alias for the result of a token stream operation. type StreamResult = syn::Result; -type VecStreamResult = Result, syn::Error>; +/// A type alias for the result of a vector of token streams. +type VecStreamResult = syn::Result>; +/// A constant representing the mandatory prefix. +/// Used for making constants in the form const MANDATORY_PREFIX: &str = "M"; +/// The `derive_builder` macro is used to automatically generate builder +/// code for a struct. It takes a struct as input and generates a builder +/// pattern implementation for that struct. +/// +/// # Example +/// +/// ```rust +/// #[derive(Builder)] +/// struct MyStruct { +/// field1: i32, +/// field2: String, +/// } +/// ``` +/// +/// This will generate a builder pattern for `MyStruct`, allowing you to +/// construct instances of `MyStruct` with a fluent API. #[proc_macro_derive(Builder, attributes(builder, group))] pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse_macro_input!(input as DeriveInput); @@ -21,6 +41,20 @@ pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream .into() } +/// This function implements the custom derive for the `Builder` trait. +/// +/// It takes a `syn::DeriveInput` as input, which represents the struct +/// for which the builder pattern is being generated. It then extracts +/// information about the struct and uses a `Generator` to generate the +/// builder pattern code. +/// +/// # Arguments +/// +/// - `ast`: A `syn::DeriveInput` representing the input struct. +/// +/// # Returns +/// +/// A `StreamResult` representing the generated token stream. fn impl_my_derive(ast: &syn::DeriveInput) -> StreamResult { let struct_info = StructInfo::new(ast)?; let generator = Generator::new(&struct_info); diff --git a/const_typed_builder_derive/src/symbol.rs b/const_typed_builder_derive/src/symbol.rs index 3715e06..791b710 100644 --- a/const_typed_builder_derive/src/symbol.rs +++ b/const_typed_builder_derive/src/symbol.rs @@ -2,18 +2,29 @@ use proc_macro2::Span; use std::fmt::Display; use syn::{Ident, Path}; +/// A `Symbol` is a wrapper around a string identifier used for constants and identifiers. #[derive(Copy, Clone, PartialEq, Eq)] pub struct Symbol<'a>(&'a str); +/// Constant representing the "mandatory" symbol. pub const MANDATORY: Symbol = Symbol("mandatory"); +/// Constant representing the "group" symbol. pub const GROUP: Symbol = Symbol("group"); +/// Constant representing the "builder" symbol. pub const BUILDER: Symbol = Symbol("builder"); +/// Constant representing the "single" symbol. pub const SINGLE: Symbol = Symbol("single"); +/// Constant representing the "at_least" symbol. pub const AT_LEAST: Symbol = Symbol("at_least"); +/// Constant representing the "at_most" symbol. pub const AT_MOST: Symbol = Symbol("at_most"); +/// Constant representing the "exact" symbol. pub const EXACT: Symbol = Symbol("exact"); +/// Constant representing the "propagate" symbol. pub const PROPAGATE: Symbol = Symbol("propagate"); +/// Constant representing the "assume_mandatory" symbol. pub const ASSUME_MANDATORY: Symbol = Symbol("assume_mandatory"); +/// Constant representing the "optional" symbol. pub const OPTIONAL: Symbol = Symbol("optional"); impl<'a> From<&'a String> for Symbol<'a> { diff --git a/src/lib.rs b/src/lib.rs index 997fd51..d3dbe2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -167,17 +167,13 @@ /// #[group(least = at_least(2))] /// #[group(most = at_most(3))] /// pub struct Foo { -/// #[builder(group = least)] -/// #[builder(group = most)] +/// #[builder(group = least, group = most)] /// bar: Option, -/// #[builder(group = least)] -/// #[builder(group = most)] +/// #[builder(group = least, group = most)] /// baz: Option, -/// #[builder(group = least)] -/// #[builder(group = most)] +/// #[builder(group = least, group = most)] /// qux: Option, -/// #[builder(group = least)] -/// #[builder(group = most)] +/// #[builder(group = least, group = most)] /// quz: Option, /// #[builder(group = most)] /// fred: Option, @@ -229,13 +225,13 @@ /// #[derive(Debug, Builder)] /// pub struct Foo /// where -/// A: Default, +/// A: Into, /// { /// bar: A, /// } /// -/// let foo = Foo::::builder() -/// .bar("Hello world!".to_string()) +/// let foo = Foo::::builder() +/// .bar(42) /// .build(); /// ``` /// From bf39ad1317bd2a16b8ce0f438bb04f1aaccc86a3 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Thu, 14 Sep 2023 10:26:12 +0200 Subject: [PATCH 02/65] Add effects to README.md (#10) --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e8625a5..86ac152 100644 --- a/README.md +++ b/README.md @@ -17,5 +17,72 @@ Also, make sure you have the following import statements in your code: ```rust use const_typed_builder::Builder; ``` +## Effects +This derive: +```rust +use const_typed_builder::Builder; +#[derive(Debug, Builder)] +pub struct Foo { + bar: String, +} +``` +Expands to this code: +```rust +use const_typed_builder::Builder; +#[derive(Debug)] +pub struct Foo { + bar: String, +} +impl Builder for Foo { + type BuilderImpl = FooBuilder; + fn builder() -> Self::BuilderImpl { + Self::BuilderImpl::new() + } +} +#[derive(Debug)] +pub struct FooBuilder { + data: FooData, +} +impl FooBuilder { + pub fn new() -> FooBuilder { + Self::default() + } +} +impl Default for FooBuilder { + fn default() -> Self { + FooBuilder { + data: FooData::default(), + } + } +} +impl FooBuilder { + pub fn bar(self, bar: String) -> FooBuilder { + let mut data = self.data; + data.bar = Some(bar); + FooBuilder { data } + } +} +impl FooBuilder { + pub fn build(self) -> Foo { + self.data.into() + } +} +#[derive(Debug)] +pub struct FooData { + pub bar: Option, +} +impl From for Foo { + fn from(data: FooData) -> Foo { + Foo { + bar: data.bar.unwrap(), + } + } +} +impl Default for FooData { + fn default() -> Self { + FooData { bar: None } + } +} +``` ## Inspirations -Builder macros have been done before, but not exactly what I needed for my use case. Also look into [derive_builder](https://crates.io/crates/derive_builder) and [typed-builder](https://crates.io/crates/typed-builder). Those projects are currently way more mature, but anyone willing to test this crate is currently a godsend. \ No newline at end of file +Builder macros have been done before, but not exactly what I needed for my use case. Also look into [derive_builder](https://crates.io/crates/derive_builder) and [typed-builder](https://crates.io/crates/typed-builder). Those projects are currently way more mature, but anyone willing to test this crate is currently a godsend. From ad3cd534170440d605f1337139503b2c3b3380e8 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Thu, 14 Sep 2023 13:42:50 +0200 Subject: [PATCH 03/65] Add test package (#11) * Add test package with some compilation fail checks * Update CI * Run fmt and update workflow * Get workflow to run all tests in workspace --- .github/workflows/rust.yml | 18 +- Cargo.toml | 5 +- const_typed_builder_derive/src/lib.rs | 2 +- const_typed_builder_test/Cargo.toml | 18 + .../compile_fail/mixed_mandatory.rs | 17 + .../compile_fail/mixed_mandatory.stderr | 18 + .../compile_fail/multiple_mandatory.rs | 17 + .../compile_fail/multiple_mandatory.stderr | 18 + .../compile_fail/option_explicit.rs | 10 + .../compile_fail/option_explicit.stderr | 19 + .../compile_fail/option_not_mandatory.rs | 9 + .../compile_fail/option_not_mandatory.stderr | 19 + .../compile_fail/optional_mandatory.rs | 11 + .../compile_fail/optional_mandatory.stderr | 15 + .../compile_fail/optional_mandatory_set.rs | 11 + .../optional_mandatory_set.stderr | 15 + .../compile_fail/simple_mandatory.rs | 11 + .../compile_fail/simple_mandatory.stderr | 15 + const_typed_builder_test/src/lib.rs | 711 ++++++++++++++++++ src/lib.rs | 700 ----------------- 20 files changed, 952 insertions(+), 707 deletions(-) create mode 100644 const_typed_builder_test/Cargo.toml create mode 100644 const_typed_builder_test/compile_fail/mixed_mandatory.rs create mode 100644 const_typed_builder_test/compile_fail/mixed_mandatory.stderr create mode 100644 const_typed_builder_test/compile_fail/multiple_mandatory.rs create mode 100644 const_typed_builder_test/compile_fail/multiple_mandatory.stderr create mode 100644 const_typed_builder_test/compile_fail/option_explicit.rs create mode 100644 const_typed_builder_test/compile_fail/option_explicit.stderr create mode 100644 const_typed_builder_test/compile_fail/option_not_mandatory.rs create mode 100644 const_typed_builder_test/compile_fail/option_not_mandatory.stderr create mode 100644 const_typed_builder_test/compile_fail/optional_mandatory.rs create mode 100644 const_typed_builder_test/compile_fail/optional_mandatory.stderr create mode 100644 const_typed_builder_test/compile_fail/optional_mandatory_set.rs create mode 100644 const_typed_builder_test/compile_fail/optional_mandatory_set.stderr create mode 100644 const_typed_builder_test/compile_fail/simple_mandatory.rs create mode 100644 const_typed_builder_test/compile_fail/simple_mandatory.stderr create mode 100644 const_typed_builder_test/src/lib.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d4a762b..433eb24 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,17 +9,25 @@ on: env: CARGO_TERM_COLOR: always + + jobs: - test: + lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose - name: Check formatting run: cargo fmt --check --verbose - name: Run Clippy run: cargo clippy --verbose -- -D warnings + + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run all tests in workspace + run: cargo test --workspace --verbose diff --git a/Cargo.toml b/Cargo.toml index 91ec2c2..41a6980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "./const_typed_builder_derive"] +members = [".", "./const_typed_builder_derive", "./const_typed_builder_test"] [workspace.package] description = "Compile-time type-checked builder derive using const generics" @@ -28,3 +28,6 @@ readme.workspace = true [dependencies] const_typed_builder_derive = { path = "const_typed_builder_derive", version = "=0.1.1" } + +[dev-dependencies] +trybuild = "1.0.84" diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index 779eb86..3b8413d 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -23,7 +23,7 @@ const MANDATORY_PREFIX: &str = "M"; /// /// # Example /// -/// ```rust +/// ```ignore /// #[derive(Builder)] /// struct MyStruct { /// field1: i32, diff --git a/const_typed_builder_test/Cargo.toml b/const_typed_builder_test/Cargo.toml new file mode 100644 index 0000000..04b1b44 --- /dev/null +++ b/const_typed_builder_test/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "const_typed_builder_test" +description.workspace = true +version.workspace = true +authors.workspace = true +repository.workspace = true +edition.workspace = true +keywords.workspace = true +categories.workspace = true +license.workspace = true +readme.workspace = true +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dev-dependencies] +const_typed_builder = { path = "../../const_typed_builder", version = "=0.1.1" } +trybuild = "1.0.84" \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/mixed_mandatory.rs b/const_typed_builder_test/compile_fail/mixed_mandatory.rs new file mode 100644 index 0000000..8ff2658 --- /dev/null +++ b/const_typed_builder_test/compile_fail/mixed_mandatory.rs @@ -0,0 +1,17 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: String, + baz: String, + qux: Option, + quz: Option, + } + + let foo = Foo::builder() + .bar("Hello".to_string()) + .qux(Some("Hello".to_string())) + .quz(Some("world!".to_string())) + .build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/mixed_mandatory.stderr b/const_typed_builder_test/compile_fail/mixed_mandatory.stderr new file mode 100644 index 0000000..6d2c801 --- /dev/null +++ b/const_typed_builder_test/compile_fail/mixed_mandatory.stderr @@ -0,0 +1,18 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/mixed_mandatory.rs:16:10 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +12 | let foo = Foo::builder() + | _______________- +13 | | .bar("Hello".to_string()) +14 | | .qux(Some("Hello".to_string())) +15 | | .quz(Some("world!".to_string())) +16 | | .build(); + | | -^^^^^ method not found in `FooBuilder` + | |_________| + | + | + = note: the method was found for + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/multiple_mandatory.rs b/const_typed_builder_test/compile_fail/multiple_mandatory.rs new file mode 100644 index 0000000..c931994 --- /dev/null +++ b/const_typed_builder_test/compile_fail/multiple_mandatory.rs @@ -0,0 +1,17 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: String, + baz: String, + qux: String, + quz: String, + } + + let foo = Foo::builder() + .bar("Hello".to_string()) + .baz("world!".to_string()) + .qux("Hello".to_string()) + .build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/multiple_mandatory.stderr b/const_typed_builder_test/compile_fail/multiple_mandatory.stderr new file mode 100644 index 0000000..c40da37 --- /dev/null +++ b/const_typed_builder_test/compile_fail/multiple_mandatory.stderr @@ -0,0 +1,18 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/multiple_mandatory.rs:16:10 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +12 | let foo = Foo::builder() + | _______________- +13 | | .bar("Hello".to_string()) +14 | | .baz("world!".to_string()) +15 | | .qux("Hello".to_string()) +16 | | .build(); + | | -^^^^^ method not found in `FooBuilder` + | |_________| + | + | + = note: the method was found for + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/option_explicit.rs b/const_typed_builder_test/compile_fail/option_explicit.rs new file mode 100644 index 0000000..16080e7 --- /dev/null +++ b/const_typed_builder_test/compile_fail/option_explicit.rs @@ -0,0 +1,10 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: std::option::Option, + } + + let foo = Foo::builder().bar("Hello world!".to_string()); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/option_explicit.stderr b/const_typed_builder_test/compile_fail/option_explicit.stderr new file mode 100644 index 0000000..a94303c --- /dev/null +++ b/const_typed_builder_test/compile_fail/option_explicit.stderr @@ -0,0 +1,19 @@ +error[E0308]: mismatched types + --> ./compile_fail/option_explicit.rs:9:34 + | +9 | let foo = Foo::builder().bar("Hello world!".to_string()); + | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Option`, found `String` + | | + | arguments to this method are incorrect + | + = note: expected enum `Option` + found struct `String` +note: method defined here + --> ./compile_fail/option_explicit.rs:6:9 + | +6 | bar: std::option::Option, + | ^^^----------------------------- +help: try wrapping the expression in `Some` + | +9 | let foo = Foo::builder().bar(Some("Hello world!".to_string())); + | +++++ + diff --git a/const_typed_builder_test/compile_fail/option_not_mandatory.rs b/const_typed_builder_test/compile_fail/option_not_mandatory.rs new file mode 100644 index 0000000..067f34a --- /dev/null +++ b/const_typed_builder_test/compile_fail/option_not_mandatory.rs @@ -0,0 +1,9 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: Option, + } + let foo = Foo::builder().bar("Hello world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/option_not_mandatory.stderr b/const_typed_builder_test/compile_fail/option_not_mandatory.stderr new file mode 100644 index 0000000..0cdcb57 --- /dev/null +++ b/const_typed_builder_test/compile_fail/option_not_mandatory.stderr @@ -0,0 +1,19 @@ +error[E0308]: mismatched types + --> ./compile_fail/option_not_mandatory.rs:8:34 + | +8 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); + | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Option`, found `String` + | | + | arguments to this method are incorrect + | + = note: expected enum `Option` + found struct `String` +note: method defined here + --> ./compile_fail/option_not_mandatory.rs:6:9 + | +6 | bar: Option, + | ^^^---------------- +help: try wrapping the expression in `Some` + | +8 | let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); + | +++++ + diff --git a/const_typed_builder_test/compile_fail/optional_mandatory.rs b/const_typed_builder_test/compile_fail/optional_mandatory.rs new file mode 100644 index 0000000..5ae2524 --- /dev/null +++ b/const_typed_builder_test/compile_fail/optional_mandatory.rs @@ -0,0 +1,11 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(mandatory)] + bar: Option, + } + + let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/optional_mandatory.stderr b/const_typed_builder_test/compile_fail/optional_mandatory.stderr new file mode 100644 index 0000000..219615d --- /dev/null +++ b/const_typed_builder_test/compile_fail/optional_mandatory.stderr @@ -0,0 +1,15 @@ +error[E0308]: mismatched types + --> ./compile_fail/optional_mandatory.rs:10:34 + | +10 | let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); + | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `String`, found `Option` + | | + | arguments to this method are incorrect + | + = note: expected struct `String` + found enum `Option` +note: method defined here + --> ./compile_fail/optional_mandatory.rs:7:9 + | +7 | bar: Option, + | ^^^--------------- diff --git a/const_typed_builder_test/compile_fail/optional_mandatory_set.rs b/const_typed_builder_test/compile_fail/optional_mandatory_set.rs new file mode 100644 index 0000000..a02bdfa --- /dev/null +++ b/const_typed_builder_test/compile_fail/optional_mandatory_set.rs @@ -0,0 +1,11 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(mandatory = true)] + bar: Option, + } + + let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/optional_mandatory_set.stderr b/const_typed_builder_test/compile_fail/optional_mandatory_set.stderr new file mode 100644 index 0000000..d9d23ad --- /dev/null +++ b/const_typed_builder_test/compile_fail/optional_mandatory_set.stderr @@ -0,0 +1,15 @@ +error[E0308]: mismatched types + --> ./compile_fail/optional_mandatory_set.rs:10:34 + | +10 | let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); + | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `String`, found `Option` + | | + | arguments to this method are incorrect + | + = note: expected struct `String` + found enum `Option` +note: method defined here + --> ./compile_fail/optional_mandatory_set.rs:7:9 + | +7 | bar: Option, + | ^^^--------------- diff --git a/const_typed_builder_test/compile_fail/simple_mandatory.rs b/const_typed_builder_test/compile_fail/simple_mandatory.rs new file mode 100644 index 0000000..451228f --- /dev/null +++ b/const_typed_builder_test/compile_fail/simple_mandatory.rs @@ -0,0 +1,11 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: String, + } + + let foo = Foo::builder() + .build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/simple_mandatory.stderr b/const_typed_builder_test/compile_fail/simple_mandatory.stderr new file mode 100644 index 0000000..0ccc494 --- /dev/null +++ b/const_typed_builder_test/compile_fail/simple_mandatory.stderr @@ -0,0 +1,15 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/simple_mandatory.rs:10:10 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +9 | let foo = Foo::builder() + | _______________- +10 | | .build(); + | | -^^^^^ method not found in `FooBuilder` + | |_________| + | + | + = note: the method was found for + - `FooBuilder` diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs new file mode 100644 index 0000000..50d9dbe --- /dev/null +++ b/const_typed_builder_test/src/lib.rs @@ -0,0 +1,711 @@ +#[cfg(test)] +mod test { + use const_typed_builder::Builder; + + #[test] + fn compile_fail_tests() { + let test_cases = trybuild::TestCases::new(); + let test_dir = std::fs::read_dir("./compile_fail").expect("Can't find test directory"); + test_dir.for_each(|entry| { + let entry = entry.expect("Can't find test entry"); + if entry.path().extension() == Some(std::ffi::OsStr::new("rs")) { + test_cases.compile_fail(entry.path()) + } + }) + } + + #[test] + fn single_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: String, + } + + let expected = Foo { + bar: "Hello world!".to_string(), + }; + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + + // let foo = Foo::builder().build(); + } + + #[test] + fn option_not_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: Option, + } + let foo = Foo::builder().build(); + let expected = Foo { bar: None }; + assert_eq!(expected, foo); + + let foo = Foo::builder().bar(None).build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: Some("Hello world!".to_string()), + }; + let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); + assert_eq!(expected, foo); + } + + #[test] + fn multiple_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: String, + baz: String, + qux: String, + quz: String, + } + + let expected = Foo { + bar: "Hello".to_string(), + baz: "world!".to_string(), + qux: "Hello".to_string(), + quz: "world!".to_string(), + }; + let foo = Foo::builder() + .bar("Hello".to_string()) + .baz("world!".to_string()) + .qux("Hello".to_string()) + .quz("world!".to_string()) + .build(); + assert_eq!(expected, foo); + } + + #[test] + fn mixed_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: String, + baz: String, + qux: Option, + quz: Option, + } + + let expected = Foo { + bar: "Hello".to_string(), + baz: "world!".to_string(), + qux: Some("Hello".to_string()), + quz: Some("world!".to_string()), + }; + let foo = Foo::builder() + .bar("Hello".to_string()) + .baz("world!".to_string()) + .qux(Some("Hello".to_string())) + .quz(Some("world!".to_string())) + .build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: "Hello".to_string(), + baz: "world!".to_string(), + qux: Some("Hello".to_string()), + quz: None, + }; + let foo = Foo::builder() + .bar("Hello".to_string()) + .baz("world!".to_string()) + .qux(Some("Hello".to_string())) + .build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: "Hello".to_string(), + baz: "world!".to_string(), + qux: None, + quz: None, + }; + let foo = Foo::builder() + .bar("Hello".to_string()) + .baz("world!".to_string()) + .build(); + assert_eq!(expected, foo); + + let foo = Foo::builder() + .bar("Hello".to_string()) + .baz("world!".to_string()) + .qux(None) + .quz(None) + .build(); + assert_eq!(expected, foo); + } + + #[test] + fn option_explicit() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: std::option::Option, + } + let foo = Foo::builder().build(); + let expected = Foo { bar: None }; + assert_eq!(expected, foo); + + let foo = Foo::builder().bar(None).build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: Some("Hello world!".to_string()), + }; + let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); + assert_eq!(expected, foo); + + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Bar { + baz: core::option::Option, + } + let bar = Bar::builder().build(); + let expected = Bar { baz: None }; + assert_eq!(expected, bar); + + let bar = Bar::builder().baz(None).build(); + assert_eq!(expected, bar); + + let expected = Bar { + baz: Some("Hello world!".to_string()), + }; + let bar = Bar::builder().baz(Some("Hello world!".to_string())).build(); + assert_eq!(expected, bar); + } + + #[test] + fn optional_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(mandatory)] + bar: Option, + } + + let expected = Foo { + bar: Some("Hello world!".to_string()), + }; + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + } + + #[test] + fn optional_mandatory_set() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(mandatory = true)] + bar: Option, + } + + let expected = Foo { + bar: Some("Hello world!".to_string()), + }; + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + } + + #[test] + fn assume_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[builder(assume_mandatory)] + pub struct Foo { + bar: Option, + } + + let expected = Foo { + bar: Some("Hello world!".to_string()), + }; + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + } + + #[test] + fn assume_mandatory_explicit_optional() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[builder(assume_mandatory)] + pub struct Foo { + bar: Option, + baz: Option, + #[builder(optional)] + quz: Option, + } + + let expected = Foo { + bar: Some("Hello world!".to_string()), + baz: Some("Hello world!".to_string()), + quz: None, + }; + let foo = Foo::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!".to_string()) + .build(); + assert_eq!(expected, foo); + } + + #[test] + fn group() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(baz = single)] + pub struct Foo { + #[builder(group = baz)] + bar: Option, + } + + let expected = Foo { + bar: Some("Hello world!".to_string()), + }; + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + } + + #[test] + fn group_multiple_member() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(baz = single)] + pub struct Foo { + #[builder(group = baz)] + bar: Option, + #[builder(group = baz)] + qux: Option, + } + + let expected = Foo { + bar: Some("Hello world!".to_string()), + qux: None, + }; + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: None, + qux: Some("Hello world!".to_string()), + }; + let foo = Foo::builder().qux("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + + // // EXPECT: DOESNT RUN + // let expected = Foo { + // bar: Some("Hello world!".to_string()), + // qux: Some("Hello world!".to_string()), + // }; + // let foo = Foo::builder().bar("Hello world!".to_string()).qux("Hello world!".to_string()).build(); + // assert_eq!(expected, foo); + } + + #[test] + fn group_and_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = single)] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + qux: String, + } + + let expected = Foo { + bar: Some("Hello".to_string()), + baz: None, + qux: "world!".to_string(), + }; + + let foo = Foo::builder() + .bar("Hello".to_string()) + .qux("world!".to_string()) + .build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: None, + baz: Some("Hello".to_string()), + qux: "world!".to_string(), + }; + + let foo = Foo::builder() + .qux("world!".to_string()) + .baz("Hello".to_string()) + .build(); + assert_eq!(expected, foo); + + // let foo = Foo::builder().baz("Hello".to_string()).build(); + // let foo = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); + } + + #[test] + fn group_and_option_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = single)] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + #[builder(mandatory)] + qux: Option, + } + + let expected = Foo { + bar: Some("Hello".to_string()), + baz: None, + qux: Some("world!".to_string()), + }; + + let foo = Foo::builder() + .bar("Hello".to_string()) + .qux("world!".to_string()) + .build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: None, + baz: Some("Hello".to_string()), + qux: Some("world!".to_string()), + }; + + let foo = Foo::builder() + .qux("world!".to_string()) + .baz("Hello".to_string()) + .build(); + assert_eq!(expected, foo); + + // let foo = Foo::builder().baz("Hello".to_string()).build(); + // let foo = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); + + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = single)] + pub struct Nope { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + #[builder(mandatory)] + baz: Option, + #[builder(mandatory)] + qux: Option, + } + } + + #[test] + fn group_at_least() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = at_least(2))] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + #[builder(group = quz)] + qux: Option, + } + + let expected = Foo { + bar: Some("Hello".to_string()), + baz: None, + qux: Some("world!".to_string()), + }; + + let foo = Foo::builder() + .bar("Hello".to_string()) + .qux("world!".to_string()) + .build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: None, + baz: Some("Hello".to_string()), + qux: Some("world!".to_string()), + }; + + let foo = Foo::builder() + .qux("world!".to_string()) + .baz("Hello".to_string()) + .build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: Some("Hello".to_string()), + baz: Some("world".to_string()), + qux: Some("!".to_string()), + }; + + let foo = Foo::builder() + .qux("!".to_string()) + .baz("world".to_string()) + .bar("Hello".to_string()) + .build(); + assert_eq!(expected, foo); + + // let foo = Foo::builder().baz("Hello".to_string()).build(); + // let foo = Foo::builder().bar("Hello".to_string()).bar("Hello".to_string()).qux("world!".to_string()).build(); + } + + #[test] + fn group_at_most() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = at_most(2))] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + #[builder(group = quz)] + qux: Option, + } + + let expected = Foo { + bar: Some("Hello".to_string()), + baz: None, + qux: Some("world!".to_string()), + }; + + let foo = Foo::builder() + .bar("Hello".to_string()) + .qux("world!".to_string()) + .build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: None, + baz: Some("Hello".to_string()), + qux: Some("world!".to_string()), + }; + + let foo = Foo::builder() + .qux("world!".to_string()) + .baz("Hello".to_string()) + .build(); + assert_eq!(expected, foo); + + let expected = Foo { + bar: None, + baz: Some("Hello world!".to_string()), + qux: None, + }; + let foo = Foo::builder().baz("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + + // let expected = Foo { + // bar: Some("Hello".to_string()), + // baz: Some("world".to_string()), + // qux: Some("!".to_string()), + // }; + + // let foo = Foo::builder().qux("!".to_string()).baz("world".to_string()).bar("Hello".to_string()).build(); + // assert_eq!(expected, foo); + + // let foo = Foo::builder().bar("Hello".to_string()).bar("Hello".to_string()).qux("world!".to_string()).build(); + } + + #[test] + fn single_generic_added_default() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo + where + A: Default, + { + bar: A, + } + + let expected = Foo:: { + bar: "Hello world!".to_string(), + }; + let foo = Foo::::builder() + .bar("Hello world!".to_string()) + .build(); + assert_eq!(expected, foo); + + let foo: Foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + } + + #[test] + fn single_generic_multiple_mandatory_added_default() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo + where + A: Default, + { + bar: A, + baz: A, + } + + let expected = Foo:: { + bar: "Hello world!".to_string(), + baz: "Hello world!".to_string(), + }; + let foo = Foo::::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!".to_string()) + .build(); + assert_eq!(expected, foo); + + let foo: Foo = Foo::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!".to_string()) + .build(); + assert_eq!(expected, foo); + + // let foo = Foo::builder().build(); + } + + #[test] + fn single_generic_multiple_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: A, + baz: A, + } + + let expected = Foo:: { + bar: "Hello world!".to_string(), + baz: "Hello world!".to_string(), + }; + let foo = Foo::::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!".to_string()) + .build(); + assert_eq!(expected, foo); + + let foo: Foo = Foo::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!".to_string()) + .build(); + assert_eq!(expected, foo); + + // let foo = Foo::builder().build(); + } + + #[test] + fn multiple_generic_multiple_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: A, + baz: B, + } + + let expected = Foo:: { + bar: "Hello world!".to_string(), + baz: "Hello world!", + }; + let foo = Foo::::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!") + .build(); + assert_eq!(expected, foo); + + // let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); + // assert_eq!(expected, foo); + + // let foo = Foo::builder().build(); + } + + #[test] + fn multiple_generic_with_const_multiple_mandatory() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: A, + baz: B, + } + + let expected = Foo:: { + bar: "Hello world!".to_string(), + baz: "Hello world!", + }; + let foo = Foo::::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!") + .build(); + assert_eq!(expected, foo); + + // let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); + // assert_eq!(expected, foo); + + // let foo = Foo::builder().build(); + } + + #[test] + fn single_propagate() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(propagate)] + bar: Bar, + } + + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Bar { + baz: String, + } + + let expected = Foo { + bar: Bar { + baz: "Hello world!".to_string(), + }, + }; + let foo = Foo::builder() + .bar(|builder| builder.baz("Hello world!".to_string()).build()) + .build(); + assert_eq!(expected, foo); + + // let foo = Foo::builder().bar(|builder| builder.build() ).build(); + + // #[derive(Debug, Default, PartialEq, Eq, Builder)] + // pub struct Foo { + // #[builder(propagate)] + // bar: Bar, + // } + + // #[derive(Debug, Default, PartialEq, Eq, Builder)] + // pub struct Bar { + // baz: String, + // } + + // let expected = Foo { + // bar: Bar { + // baz: "Hello world!".to_string(), + // } + // }; + // let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build() ).build(); + // assert_eq!(expected, foo); + } + + #[test] + fn optional_propagate() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(propagate)] + bar: Option, + } + + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Bar { + baz: String, + } + + let expected = Foo { + bar: Some(Bar { + baz: "Hello world!".to_string(), + }), + }; + let foo = Foo::builder() + .bar(|builder| Some(builder.baz("Hello world!".to_string()).build())) + .build(); + assert_eq!(expected, foo); + + // let foo = Foo::builder().bar(|builder| builder.build() ).build(); + + // #[derive(Debug, Default, PartialEq, Eq, Builder)] + // pub struct Foo { + // #[builder(propagate)] + // bar: Bar, + // } + + // #[derive(Debug, Default, PartialEq, Eq, Builder)] + // pub struct Bar { + // baz: String, + // } + + // let expected = Foo { + // bar: Bar { + // baz: "Hello world!".to_string(), + // } + // }; + // let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build() ).build(); + // assert_eq!(expected, foo); + } +} diff --git a/src/lib.rs b/src/lib.rs index d3dbe2e..1b354e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -242,703 +242,3 @@ pub trait Builder { type BuilderImpl; fn builder() -> Self::BuilderImpl; } - -#[cfg(test)] -mod test { - use super::Builder; - - #[test] - fn single_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - bar: String, - } - - let expected = Foo { - bar: "Hello world!".to_string(), - }; - let foo = Foo::builder().bar("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); - } - - #[test] - fn option_not_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - bar: Option, - } - let foo = Foo::builder().build(); - let expected = Foo { bar: None }; - assert_eq!(expected, foo); - - let foo = Foo::builder().bar(None).build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: Some("Hello world!".to_string()), - }; - let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); - assert_eq!(expected, foo); - } - - #[test] - fn multiple_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - bar: String, - baz: String, - qux: String, - quz: String, - } - - let expected = Foo { - bar: "Hello".to_string(), - baz: "world!".to_string(), - qux: "Hello".to_string(), - quz: "world!".to_string(), - }; - let foo = Foo::builder() - .bar("Hello".to_string()) - .baz("world!".to_string()) - .qux("Hello".to_string()) - .quz("world!".to_string()) - .build(); - assert_eq!(expected, foo); - } - - #[test] - fn mixed_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - bar: String, - baz: String, - qux: Option, - quz: Option, - } - - let expected = Foo { - bar: "Hello".to_string(), - baz: "world!".to_string(), - qux: Some("Hello".to_string()), - quz: Some("world!".to_string()), - }; - let foo = Foo::builder() - .bar("Hello".to_string()) - .baz("world!".to_string()) - .qux(Some("Hello".to_string())) - .quz(Some("world!".to_string())) - .build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: "Hello".to_string(), - baz: "world!".to_string(), - qux: Some("Hello".to_string()), - quz: None, - }; - let foo = Foo::builder() - .bar("Hello".to_string()) - .baz("world!".to_string()) - .qux(Some("Hello".to_string())) - .build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: "Hello".to_string(), - baz: "world!".to_string(), - qux: None, - quz: None, - }; - let foo = Foo::builder() - .bar("Hello".to_string()) - .baz("world!".to_string()) - .build(); - assert_eq!(expected, foo); - - let foo = Foo::builder() - .bar("Hello".to_string()) - .baz("world!".to_string()) - .qux(None) - .quz(None) - .build(); - assert_eq!(expected, foo); - } - - #[test] - fn option_explicit() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - bar: std::option::Option, - } - let foo = Foo::builder().build(); - let expected = Foo { bar: None }; - assert_eq!(expected, foo); - - let foo = Foo::builder().bar(None).build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: Some("Hello world!".to_string()), - }; - let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); - assert_eq!(expected, foo); - - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Bar { - baz: core::option::Option, - } - let bar = Bar::builder().build(); - let expected = Bar { baz: None }; - assert_eq!(expected, bar); - - let bar = Bar::builder().baz(None).build(); - assert_eq!(expected, bar); - - let expected = Bar { - baz: Some("Hello world!".to_string()), - }; - let bar = Bar::builder().baz(Some("Hello world!".to_string())).build(); - assert_eq!(expected, bar); - } - - #[test] - fn optional_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - #[builder(mandatory)] - bar: Option, - } - - let expected = Foo { - bar: Some("Hello world!".to_string()), - }; - let foo = Foo::builder().bar("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - } - - #[test] - fn optional_mandatory_set() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - #[builder(mandatory = true)] - bar: Option, - } - - let expected = Foo { - bar: Some("Hello world!".to_string()), - }; - let foo = Foo::builder().bar("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - } - - #[test] - fn assume_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[builder(assume_mandatory)] - pub struct Foo { - bar: Option, - } - - let expected = Foo { - bar: Some("Hello world!".to_string()), - }; - let foo = Foo::builder().bar("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - } - - #[test] - fn assume_mandatory_explicit_optional() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[builder(assume_mandatory)] - pub struct Foo { - bar: Option, - baz: Option, - #[builder(optional)] - quz: Option, - } - - let expected = Foo { - bar: Some("Hello world!".to_string()), - baz: Some("Hello world!".to_string()), - quz: None, - }; - let foo = Foo::builder() - .bar("Hello world!".to_string()) - .baz("Hello world!".to_string()) - .build(); - assert_eq!(expected, foo); - } - - #[test] - fn group() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[group(baz = single)] - pub struct Foo { - #[builder(group = baz)] - bar: Option, - } - - let expected = Foo { - bar: Some("Hello world!".to_string()), - }; - let foo = Foo::builder().bar("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - } - - #[test] - fn group_multiple_member() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[group(baz = single)] - pub struct Foo { - #[builder(group = baz)] - bar: Option, - #[builder(group = baz)] - qux: Option, - } - - let expected = Foo { - bar: Some("Hello world!".to_string()), - qux: None, - }; - let foo = Foo::builder().bar("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: None, - qux: Some("Hello world!".to_string()), - }; - let foo = Foo::builder().qux("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - - // // EXPECT: DOESNT RUN - // let expected = Foo { - // bar: Some("Hello world!".to_string()), - // qux: Some("Hello world!".to_string()), - // }; - // let foo = Foo::builder().bar("Hello world!".to_string()).qux("Hello world!".to_string()).build(); - // assert_eq!(expected, foo); - } - - #[test] - fn group_and_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[group(quz = single)] - pub struct Foo { - #[builder(group = quz)] - bar: Option, - #[builder(group = quz)] - baz: Option, - qux: String, - } - - let expected = Foo { - bar: Some("Hello".to_string()), - baz: None, - qux: "world!".to_string(), - }; - - let foo = Foo::builder() - .bar("Hello".to_string()) - .qux("world!".to_string()) - .build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: None, - baz: Some("Hello".to_string()), - qux: "world!".to_string(), - }; - - let foo = Foo::builder() - .qux("world!".to_string()) - .baz("Hello".to_string()) - .build(); - assert_eq!(expected, foo); - - // let foo = Foo::builder().baz("Hello".to_string()).build(); - // let foo = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); - } - - #[test] - fn group_and_option_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[group(quz = single)] - pub struct Foo { - #[builder(group = quz)] - bar: Option, - #[builder(group = quz)] - baz: Option, - #[builder(mandatory)] - qux: Option, - } - - let expected = Foo { - bar: Some("Hello".to_string()), - baz: None, - qux: Some("world!".to_string()), - }; - - let foo = Foo::builder() - .bar("Hello".to_string()) - .qux("world!".to_string()) - .build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: None, - baz: Some("Hello".to_string()), - qux: Some("world!".to_string()), - }; - - let foo = Foo::builder() - .qux("world!".to_string()) - .baz("Hello".to_string()) - .build(); - assert_eq!(expected, foo); - - // let foo = Foo::builder().baz("Hello".to_string()).build(); - // let foo = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); - - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[group(quz = single)] - pub struct Nope { - #[builder(group = quz)] - bar: Option, - #[builder(group = quz)] - #[builder(mandatory)] - baz: Option, - #[builder(mandatory)] - qux: Option, - } - } - - #[test] - fn group_at_least() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[group(quz = at_least(2))] - pub struct Foo { - #[builder(group = quz)] - bar: Option, - #[builder(group = quz)] - baz: Option, - #[builder(group = quz)] - qux: Option, - } - - let expected = Foo { - bar: Some("Hello".to_string()), - baz: None, - qux: Some("world!".to_string()), - }; - - let foo = Foo::builder() - .bar("Hello".to_string()) - .qux("world!".to_string()) - .build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: None, - baz: Some("Hello".to_string()), - qux: Some("world!".to_string()), - }; - - let foo = Foo::builder() - .qux("world!".to_string()) - .baz("Hello".to_string()) - .build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: Some("Hello".to_string()), - baz: Some("world".to_string()), - qux: Some("!".to_string()), - }; - - let foo = Foo::builder() - .qux("!".to_string()) - .baz("world".to_string()) - .bar("Hello".to_string()) - .build(); - assert_eq!(expected, foo); - - // let foo = Foo::builder().baz("Hello".to_string()).build(); - // let foo = Foo::builder().bar("Hello".to_string()).bar("Hello".to_string()).qux("world!".to_string()).build(); - } - - #[test] - fn group_at_most() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[group(quz = at_most(2))] - pub struct Foo { - #[builder(group = quz)] - bar: Option, - #[builder(group = quz)] - baz: Option, - #[builder(group = quz)] - qux: Option, - } - - let expected = Foo { - bar: Some("Hello".to_string()), - baz: None, - qux: Some("world!".to_string()), - }; - - let foo = Foo::builder() - .bar("Hello".to_string()) - .qux("world!".to_string()) - .build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: None, - baz: Some("Hello".to_string()), - qux: Some("world!".to_string()), - }; - - let foo = Foo::builder() - .qux("world!".to_string()) - .baz("Hello".to_string()) - .build(); - assert_eq!(expected, foo); - - let expected = Foo { - bar: None, - baz: Some("Hello world!".to_string()), - qux: None, - }; - let foo = Foo::builder().baz("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - - // let expected = Foo { - // bar: Some("Hello".to_string()), - // baz: Some("world".to_string()), - // qux: Some("!".to_string()), - // }; - - // let foo = Foo::builder().qux("!".to_string()).baz("world".to_string()).bar("Hello".to_string()).build(); - // assert_eq!(expected, foo); - - // let foo = Foo::builder().bar("Hello".to_string()).bar("Hello".to_string()).qux("world!".to_string()).build(); - } - - #[test] - fn single_generic_added_default() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo - where - A: Default, - { - bar: A, - } - - let expected = Foo:: { - bar: "Hello world!".to_string(), - }; - let foo = Foo::::builder() - .bar("Hello world!".to_string()) - .build(); - assert_eq!(expected, foo); - - let foo: Foo = Foo::builder().bar("Hello world!".to_string()).build(); - assert_eq!(expected, foo); - } - - #[test] - fn single_generic_multiple_mandatory_added_default() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo - where - A: Default, - { - bar: A, - baz: A, - } - - let expected = Foo:: { - bar: "Hello world!".to_string(), - baz: "Hello world!".to_string(), - }; - let foo = Foo::::builder() - .bar("Hello world!".to_string()) - .baz("Hello world!".to_string()) - .build(); - assert_eq!(expected, foo); - - let foo: Foo = Foo::builder() - .bar("Hello world!".to_string()) - .baz("Hello world!".to_string()) - .build(); - assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); - } - - #[test] - fn single_generic_multiple_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - bar: A, - baz: A, - } - - let expected = Foo:: { - bar: "Hello world!".to_string(), - baz: "Hello world!".to_string(), - }; - let foo = Foo::::builder() - .bar("Hello world!".to_string()) - .baz("Hello world!".to_string()) - .build(); - assert_eq!(expected, foo); - - let foo: Foo = Foo::builder() - .bar("Hello world!".to_string()) - .baz("Hello world!".to_string()) - .build(); - assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); - } - - #[test] - fn multiple_generic_multiple_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - bar: A, - baz: B, - } - - let expected = Foo:: { - bar: "Hello world!".to_string(), - baz: "Hello world!", - }; - let foo = Foo::::builder() - .bar("Hello world!".to_string()) - .baz("Hello world!") - .build(); - assert_eq!(expected, foo); - - // let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); - // assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); - } - - #[test] - fn multiple_generic_with_const_multiple_mandatory() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - bar: A, - baz: B, - } - - let expected = Foo:: { - bar: "Hello world!".to_string(), - baz: "Hello world!", - }; - let foo = Foo::::builder() - .bar("Hello world!".to_string()) - .baz("Hello world!") - .build(); - assert_eq!(expected, foo); - - // let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); - // assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); - } - - #[test] - fn single_propagate() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - #[builder(propagate)] - bar: Bar, - } - - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Bar { - baz: String, - } - - let expected = Foo { - bar: Bar { - baz: "Hello world!".to_string(), - }, - }; - let foo = Foo::builder() - .bar(|builder| builder.baz("Hello world!".to_string()).build()) - .build(); - assert_eq!(expected, foo); - - // let foo = Foo::builder().bar(|builder| builder.build() ).build(); - - // #[derive(Debug, Default, PartialEq, Eq, Builder)] - // pub struct Foo { - // #[builder(propagate)] - // bar: Bar, - // } - - // #[derive(Debug, Default, PartialEq, Eq, Builder)] - // pub struct Bar { - // baz: String, - // } - - // let expected = Foo { - // bar: Bar { - // baz: "Hello world!".to_string(), - // } - // }; - // let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build() ).build(); - // assert_eq!(expected, foo); - } - - #[test] - fn optional_propagate() { - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Foo { - #[builder(propagate)] - bar: Option, - } - - #[derive(Debug, Default, PartialEq, Eq, Builder)] - pub struct Bar { - baz: String, - } - - let expected = Foo { - bar: Some(Bar { - baz: "Hello world!".to_string(), - }), - }; - let foo = Foo::builder() - .bar(|builder| Some(builder.baz("Hello world!".to_string()).build())) - .build(); - assert_eq!(expected, foo); - - // let foo = Foo::builder().bar(|builder| builder.build() ).build(); - - // #[derive(Debug, Default, PartialEq, Eq, Builder)] - // pub struct Foo { - // #[builder(propagate)] - // bar: Bar, - // } - - // #[derive(Debug, Default, PartialEq, Eq, Builder)] - // pub struct Bar { - // baz: String, - // } - - // let expected = Foo { - // bar: Bar { - // baz: "Hello world!".to_string(), - // } - // }; - // let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build() ).build(); - // assert_eq!(expected, foo); - } -} From bcee99ddd5f361c4b2256bf38f85cd1f8c73dd00 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:26:14 +0200 Subject: [PATCH 04/65] Feature/decrease const amount (#20) * Get it to work * Remove unnessecary flat_map calls * Clean up * Fix merge issues --- .../src/generator/field_generator.rs | 29 +- .../src/generator/generics_generator.rs | 131 ++----- .../src/generator/group_generator.rs | 12 +- .../src/info/field_info.rs | 320 ++++-------------- .../src/info/group_info.rs | 32 +- const_typed_builder_derive/src/info/mod.rs | 2 +- .../src/info/struct_info.rs | 29 +- const_typed_builder_derive/src/lib.rs | 4 +- 8 files changed, 141 insertions(+), 418 deletions(-) diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index a09925f..da9ab6b 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -1,4 +1,7 @@ -use crate::{info::FieldInfo, VecStreamResult}; +use crate::{ + info::{FieldInfo, FieldKind}, + VecStreamResult, +}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -38,16 +41,14 @@ impl<'a> FieldGenerator<'a> { .map(|field| { let field_name = field.ident(); - let data_field_type = match field { - FieldInfo::Optional(field) => field.ty().to_token_stream(), - FieldInfo::Mandatory(field) if field.is_option_type() => { - field.ty().to_token_stream() - } - FieldInfo::Mandatory(field) => { + let data_field_type = match field.kind() { + FieldKind::Optional => field.ty().to_token_stream(), + FieldKind::Mandatory if field.is_option_type() => field.ty().to_token_stream(), + FieldKind::Mandatory => { let ty = field.ty(); quote!(Option<#ty>) } - FieldInfo::Grouped(field) => field.ty().to_token_stream(), + FieldKind::Grouped => field.ty().to_token_stream(), }; let tokens = quote!( @@ -68,14 +69,14 @@ impl<'a> FieldGenerator<'a> { .iter() .map(|field| { let field_name = field.ident(); - let tokens = match field { - FieldInfo::Mandatory(field) if field.is_option_type() => { + let tokens = match field.kind() { + FieldKind::Mandatory if field.is_option_type() => { quote!(#field_name: data.#field_name) } - FieldInfo::Optional(_) | FieldInfo::Grouped(_) => { + FieldKind::Optional | FieldKind::Grouped => { quote!(#field_name: data.#field_name) } - FieldInfo::Mandatory(_) => { + FieldKind::Mandatory => { quote!(#field_name: data.#field_name.unwrap()) } }; @@ -151,8 +152,8 @@ impl<'a> FieldGenerator<'a> { quote!(#field_name) }; - match field { - FieldInfo::Optional(_) => quote!(#field_value), + match field.kind() { + FieldKind::Optional => quote!(#field_value), _ => quote!(Some(#field_value)), } } diff --git a/const_typed_builder_derive/src/generator/generics_generator.rs b/const_typed_builder_derive/src/generator/generics_generator.rs index e99d4e3..8763784 100644 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ b/const_typed_builder_derive/src/generator/generics_generator.rs @@ -1,7 +1,7 @@ -use crate::{info::FieldInfo, MANDATORY_PREFIX}; +use crate::{info::FieldInfo, info::FieldKind}; use either::Either; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use quote::{quote, ToTokens}; use syn::parse_quote; /// The `GenericsGenerator` struct is responsible for generating code related to generics in the target struct, builder, and data types. @@ -44,20 +44,12 @@ impl<'a> GenericsGenerator<'a> { /// /// A `TokenStream` representing the generated const generics. pub fn const_generics_valued(&self, value: bool) -> TokenStream { - let mut all = self.fields.iter().flat_map(|field| match field { - FieldInfo::Optional(_) => Box::new(std::iter::empty()) - as Box>>, - FieldInfo::Mandatory(mandatory) => Box::new(std::iter::once(Either::Right( - syn::LitBool::new(value, mandatory.ident().span()), - ))) - as Box>>, - FieldInfo::Grouped(grouped) => { - Box::new( - grouped.group_indices().iter().map(|(_, _)| { - Either::Right(syn::LitBool::new(value, grouped.ident().span())) - }), - ) as Box>> - } + let mut all = self.fields.iter().filter_map(|field| match field.kind() { + FieldKind::Optional => None, + FieldKind::Mandatory | FieldKind::Grouped => Some(Either::Right(syn::LitBool::new( + value, + field.ident().span(), + ))), }); self.add_const_generics_valued_for_type(&mut all) } @@ -77,32 +69,13 @@ impl<'a> GenericsGenerator<'a> { field_info: &FieldInfo, value: bool, ) -> TokenStream { - let mut all = self.fields.iter().flat_map(|field| match field { - FieldInfo::Optional(_) => Box::new(std::iter::empty()) - as Box>>, - FieldInfo::Mandatory(_) if field_info == field => Box::new(std::iter::once( - Either::Right(syn::LitBool::new(value, field_info.ident().span())), - )) - as Box>>, - FieldInfo::Mandatory(mandatory) => Box::new(std::iter::once(Either::Left( - format_ident!("{}_{}", MANDATORY_PREFIX, mandatory.mandatory_index()), - ))) - as Box>>, - FieldInfo::Grouped(grouped) if field_info == field => Box::new( - std::iter::repeat(Either::Right(syn::LitBool::new( - value, - grouped.ident().span(), - ))) - .take(grouped.group_indices().len()), - ) - as Box>>, - FieldInfo::Grouped(grouped) => Box::new( - grouped - .group_indices() - .iter() - .map(|(group, index)| Either::Left(group.partial_const_ident(*index))), - ) - as Box>>, + let mut all = self.fields.iter().filter_map(|field| match field.kind() { + FieldKind::Optional => None, + _ if field == field_info => Some(Either::Right(syn::LitBool::new( + value, + field_info.ident().span(), + ))), + FieldKind::Mandatory | FieldKind::Grouped => Some(Either::Left(field.const_ident())), }); self.add_const_generics_valued_for_type(&mut all) } @@ -117,24 +90,10 @@ impl<'a> GenericsGenerator<'a> { /// /// A `syn::Generics` instance representing the generated const generics for the setter method input type. pub fn builder_const_generic_idents_set_impl(&self, field_info: &FieldInfo) -> syn::Generics { - let mut all = self.fields.iter().flat_map(|field| match field { - FieldInfo::Optional(_) => { - Box::new(std::iter::empty()) as Box> - } - _ if field == field_info => { - Box::new(std::iter::empty()) as Box> - } - FieldInfo::Mandatory(field) => Box::new(std::iter::once(format_ident!( - "{}_{}", - MANDATORY_PREFIX, - field.mandatory_index() - ))) as Box>, - FieldInfo::Grouped(field) => Box::new( - field - .group_indices() - .iter() - .map(|(group, index)| group.partial_const_ident(*index)), - ) as Box>, + let mut all = self.fields.iter().filter_map(|field| match field.kind() { + FieldKind::Optional => None, + _ if field == field_info => None, + FieldKind::Mandatory | FieldKind::Grouped => Some(field.const_ident()), }); self.add_const_generics_for_impl(&mut all) } @@ -145,21 +104,13 @@ impl<'a> GenericsGenerator<'a> { /// /// A `TokenStream` representing the generated const generics for the builder `build` method. pub fn builder_const_generic_idents_build(&self) -> TokenStream { - let mut all = self.fields.iter().flat_map(|field| match field { - FieldInfo::Optional(_) => Box::new(std::iter::empty()) - as Box>>, - FieldInfo::Mandatory(_) => Box::new(std::iter::once(Either::Right(syn::LitBool::new( + let mut all = self.fields.iter().filter_map(|field| match field.kind() { + FieldKind::Optional => None, + FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( true, proc_macro2::Span::call_site(), - )))) - as Box>>, - FieldInfo::Grouped(grouped) => Box::new( - grouped - .group_indices() - .iter() - .map(|(group, index)| Either::Left(group.partial_const_ident(*index))), - ) - as Box>>, + ))), + FieldKind::Grouped => Some(Either::Left(field.const_ident())), }); self.add_const_generics_valued_for_type(&mut all) } @@ -170,19 +121,9 @@ impl<'a> GenericsGenerator<'a> { /// /// A `syn::Generics` instance representing the generated const generics for builder group partial identifiers. pub fn builder_const_generic_group_partial_idents(&self) -> syn::Generics { - let mut all = self.fields.iter().flat_map(|field| match field { - FieldInfo::Optional(_) => { - Box::new(std::iter::empty()) as Box> - } - FieldInfo::Mandatory(_) => { - Box::new(std::iter::empty()) as Box> - } - FieldInfo::Grouped(grouped) => Box::new( - grouped - .group_indices() - .iter() - .map(|(group, index)| group.partial_const_ident(*index)), - ) as Box>, + let mut all = self.fields.iter().filter_map(|field| match field.kind() { + FieldKind::Optional | FieldKind::Mandatory => None, + FieldKind::Grouped => Some(field.const_ident()), }); self.add_const_generics_for_impl(&mut all) } @@ -193,21 +134,9 @@ impl<'a> GenericsGenerator<'a> { /// /// A `syn::Generics` instance representing the generated const generics for the builder struct. pub fn builder_struct_generics(&self) -> syn::Generics { - let mut all = self.fields.iter().flat_map(|field| match field { - FieldInfo::Optional(_) => { - Box::new(std::iter::empty()) as Box> - } - FieldInfo::Mandatory(mandatory) => Box::new(std::iter::once(format_ident!( - "M_{}", - mandatory.mandatory_index() - ))) - as Box>, - FieldInfo::Grouped(grouped) => Box::new( - grouped - .group_indices() - .iter() - .map(|(group, index)| group.partial_const_ident(*index)), - ) as Box>, + let mut all = self.fields.iter().filter_map(|field| match field.kind() { + FieldKind::Optional => None, + FieldKind::Mandatory | FieldKind::Grouped => Some(field.const_ident()), }); self.add_const_generics_for_impl(&mut all) } diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index c88d159..b6b715f 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -1,6 +1,9 @@ -use crate::info::{GroupInfo, GroupType}; +use crate::{ + info::{GroupInfo, GroupType}, + CONST_IDENT_PREFIX, +}; use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; +use quote::{format_ident, quote}; /// The `GroupGenerator` struct is responsible for generating code related to groups within the builder, including correctness checks and verifications. #[derive(Debug)] @@ -120,9 +123,8 @@ impl<'a> GroupGenerator<'a> { return None; } - let all = self.groups.iter().flat_map(|group| { - let partials = (0..group.member_count()) - .map(|index| group.partial_const_ident(index).into_token_stream()); + let all = self.groups.iter().map(|group| { + let partials = group.indices().iter().map(|index| format_ident!("{}{}", CONST_IDENT_PREFIX, index)); let function_call: syn::Ident = group.function_symbol().into(); let count = group.expected_count(); let name = group.name(); diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index 770aa07..1c454bf 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -1,37 +1,39 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; -use super::{group_info::GroupInfo, struct_info::StructSettings}; +use super::struct_info::StructSettings; use proc_macro2::Span; +use quote::format_ident; use syn::{ExprPath, Token}; use crate::{ symbol::{BUILDER, GROUP, MANDATORY, OPTIONAL, PROPAGATE}, util::{inner_type, is_option}, + CONST_IDENT_PREFIX, }; /// Represents the information about a struct field used for code generation. #[derive(Debug, PartialEq, Eq)] -pub enum FieldInfo<'a> { - /// Represents an optional field. - Optional(FieldInfoOptional<'a>), - /// Represents a mandatory field. - Mandatory(FieldInfoMandatory<'a>), - /// Represents a grouped field. - Grouped(FieldInfoGrouped<'a>), +pub struct FieldInfo<'a> { + field: &'a syn::Field, + ident: &'a syn::Ident, + index: usize, + propagate: bool, + kind: FieldKind, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum FieldKind { + Optional, + Mandatory, + Grouped, } impl<'a> FieldInfo<'a> { - /// Creates a new `FieldInfo` instance from a `syn::Field` and `StructSettings`. - /// - /// # Arguments - /// - /// - `field`: A `syn::Field` representing the input field. - /// - `struct_settings`: A mutable reference to `StructSettings`. - /// - /// # Returns - /// - /// A `syn::Result` containing the `FieldInfo` instance if successful, or an error if parsing fails. - pub fn new(field: &'a syn::Field, struct_settings: &mut StructSettings) -> syn::Result { + pub fn new( + field: &'a syn::Field, + struct_settings: &mut StructSettings, + index: usize, + ) -> syn::Result { if let syn::Field { attrs, ident: Some(ident), @@ -48,33 +50,37 @@ impl<'a> FieldInfo<'a> { .with_attrs(attrs)?; let info = if settings.mandatory { - Self::Mandatory(FieldInfoMandatory::new( + struct_settings.add_mandatory_index(index); // TODO: Check bool + Self { field, ident, - settings.propagate, - struct_settings.next_mandatory(), - )?) + index, + propagate: settings.propagate, + kind: FieldKind::Mandatory, + } } else if !settings.groups.is_empty() { - let mut group_indices = HashMap::with_capacity(settings.groups.len()); for group_name in settings.groups { - group_indices.insert( - struct_settings - .group_by_name(&group_name) - .ok_or(syn::Error::new_spanned(field, "Can't find group"))? - .clone(), - struct_settings - .next_group_index(&group_name) - .ok_or(syn::Error::new_spanned(field, "Can't find group"))?, - ); + struct_settings + .group_by_name_mut(&group_name.to_string()) + .ok_or(syn::Error::new_spanned(group_name, "Can't find group"))? + .associate(index); } - Self::Grouped(FieldInfoGrouped::new( + + Self { field, ident, - settings.propagate, - group_indices, - )?) + index, + propagate: settings.propagate, + kind: FieldKind::Grouped, + } } else { - Self::Optional(FieldInfoOptional::new(field, ident, settings.propagate)?) + Self { + field, + ident, + index, + propagate: settings.propagate, + kind: FieldKind::Optional, + } }; Ok(info) @@ -86,156 +92,6 @@ impl<'a> FieldInfo<'a> { } } - /// Retrieves the identifier of the field. - pub fn ident(&self) -> &syn::Ident { - match self { - FieldInfo::Optional(field) => field.ident(), - FieldInfo::Mandatory(field) => field.ident(), - FieldInfo::Grouped(field) => field.ident(), - } - } - - /// Retrieves whether the field's attributes indicate builder propagation. - pub fn propagate(&self) -> bool { - match self { - FieldInfo::Optional(field) => field.propagate(), - FieldInfo::Mandatory(field) => field.propagate(), - FieldInfo::Grouped(field) => field.propagate(), - } - } - - /// Checks if the field's type is an `Option`. - pub fn is_option_type(&self) -> bool { - match self { - FieldInfo::Optional(_) => true, - FieldInfo::Mandatory(field) => field.is_option_type(), - FieldInfo::Grouped(_) => true, - } - } - - /// Retrieves the inner type of the field if it is an `Option`. - pub fn inner_type(&self) -> Option<&syn::Type> { - match self { - FieldInfo::Optional(field) => Some(field.inner_type()), - FieldInfo::Mandatory(field) => field.inner_type(), - FieldInfo::Grouped(field) => Some(field.inner_type()), - } - } - - /// Retrieves the input type for the builder's setter method. - pub fn setter_input_type(&self) -> &syn::Type { - match self { - FieldInfo::Optional(field) => field.ty(), - FieldInfo::Mandatory(field) if field.is_option_type() => field - .inner_type() - .expect("Couldn't read inner type of option, even though it's marked as optional"), - FieldInfo::Mandatory(field) => field.ty(), - FieldInfo::Grouped(field) => field.inner_type(), - } - } -} - -/// Represents information about an optional field. -#[derive(Debug, PartialEq, Eq)] -pub struct FieldInfoOptional<'a> { - field: &'a syn::Field, - ident: &'a syn::Ident, - inner_ty: &'a syn::Type, - propagate: bool, -} - -impl<'a> FieldInfoOptional<'a> { - /// Creates a new `FieldInfoOptional` instance from a `syn::Field`. - /// - /// # Arguments - /// - /// - `field`: A `syn::Field` representing the input field. - /// - `ident`: A reference to the identifier of the field. - /// - `propagate`: A boolean indicating whether the field should propagate values. - /// - /// # Returns - /// - /// A `syn::Result` containing the `FieldInfoOptional` instance if successful, or an error if parsing fails. - - fn new(field: &'a syn::Field, ident: &'a syn::Ident, propagate: bool) -> syn::Result { - Ok(Self { - field, - ident, - inner_ty: inner_type(&field.ty) - .ok_or(syn::Error::new_spanned(field, "Can't find inner type"))?, - propagate, - }) - } - - /// Retrieves the type of the field. - pub fn ty(&self) -> &syn::Type { - &self.field.ty - } - - /// Retrieves the inner type of the field. - fn inner_type(&self) -> &syn::Type { - self.inner_ty - } - - /// Retrieves the identifier of the field. - pub fn ident(&self) -> &syn::Ident { - self.ident - } - - /// Checks if the field's attributes indicate builder propagation. - pub fn propagate(&self) -> bool { - self.propagate - } -} - -/// Represents information about a mandatory field. -#[derive(Debug, PartialEq, Eq)] -pub struct FieldInfoMandatory<'a> { - field: &'a syn::Field, - ident: &'a syn::Ident, - inner_ty: Option<&'a syn::Type>, - propagate: bool, - mandatory_index: usize, -} - -impl<'a> FieldInfoMandatory<'a> { - /// Creates a new `FieldInfoMandatory` instance from a `syn::Field`. - /// - /// # Arguments - /// - /// - `field`: A `syn::Field` representing the input field. - /// - `ident`: A reference to the identifier of the field. - /// - `propagate`: A boolean indicating whether the field should propagate values. - /// - `mandatory_index`: The index of the mandatory field. - /// - /// # Returns - /// - /// A `syn::Result` containing the `FieldInfoMandatory` instance if successful, or an error if parsing fails. - fn new( - field: &'a syn::Field, - ident: &'a syn::Ident, - propagate: bool, - mandatory_index: usize, - ) -> syn::Result { - Ok(Self { - field, - ident, - inner_ty: inner_type(&field.ty), - propagate, - mandatory_index, - }) - } - - /// Retrieves the type of the field. - pub fn ty(&self) -> &syn::Type { - &self.field.ty - } - - /// Retrieves the inner type of the field. - pub fn inner_type(&self) -> Option<&syn::Type> { - self.inner_ty - } - /// Retrieves the identifier of the field. pub fn ident(&self) -> &syn::Ident { self.ident @@ -246,79 +102,39 @@ impl<'a> FieldInfoMandatory<'a> { self.propagate } - /// Retrieves the index of the mandatory field. - pub fn mandatory_index(&self) -> usize { - self.mandatory_index - } - - /// Checks if the field's type is an `Option`. pub fn is_option_type(&self) -> bool { is_option(&self.field.ty) } -} - -/// Represents information about a grouped field. -#[derive(Debug, PartialEq, Eq)] -pub struct FieldInfoGrouped<'a> { - field: &'a syn::Field, - ident: &'a syn::Ident, - inner_ty: &'a syn::Type, - propagate: bool, - group_indices: HashMap, -} - -impl<'a> FieldInfoGrouped<'a> { - /// Creates a new `FieldInfoGrouped` instance from a `syn::Field`. - /// - /// # Arguments - /// - /// - `field`: A `syn::Field` representing the input field. - /// - `ident`: A reference to the identifier of the field. - /// - `propagate`: A boolean indicating whether the field should propagate values. - /// - `group_indices`: A map of `GroupInfo` to group indices. - /// - /// # Returns - /// - /// A `syn::Result` containing the `FieldInfoGrouped` instance if successful, or an error if parsing fails. - fn new( - field: &'a syn::Field, - ident: &'a syn::Ident, - propagate: bool, - group_indices: HashMap, - ) -> syn::Result { - Ok(Self { - field, - ident, - inner_ty: inner_type(&field.ty) - .ok_or(syn::Error::new_spanned(field, "Can't find inner type"))?, - propagate, - group_indices, - }) - } /// Retrieves the type of the field. pub fn ty(&self) -> &syn::Type { &self.field.ty } - /// Retrieves the inner type of the field. - pub fn inner_type(&self) -> &syn::Type { - self.inner_ty + pub fn inner_type(&self) -> Option<&syn::Type> { + inner_type(&self.field.ty) } - /// Retrieves the identifier of the field. - pub fn ident(&self) -> &syn::Ident { - self.ident + pub fn kind(&self) -> &FieldKind { + &self.kind } - /// Checks if the field's attributes indicate propagation. - pub fn propagate(&self) -> bool { - self.propagate + pub fn const_ident(&self) -> syn::Ident { + format_ident!("{}{}", CONST_IDENT_PREFIX, self.index) } - /// Retrieves the group indices associated with the field. - pub fn group_indices(&self) -> &HashMap { - &self.group_indices + /// Retrieves the input type for the builder's setter method. + pub fn setter_input_type(&self) -> &syn::Type { + match self.kind() { + FieldKind::Optional => self.ty(), + FieldKind::Mandatory if self.is_option_type() => self.inner_type().expect( + "Couldn't read inner type of option, even though it's seen as an Option type", + ), + FieldKind::Mandatory => self.ty(), + FieldKind::Grouped => self + .inner_type() + .expect("Couldn't read inner type of option, even though it's marked as grouped"), + } } } @@ -331,8 +147,7 @@ pub struct FieldSettings { pub propagate: bool, /// The input name for the builder's setter method. pub input_name: syn::Ident, - /// A set of group names associated with the field. - pub groups: HashSet, + pub groups: HashSet, } impl Default for FieldSettings { @@ -439,7 +254,7 @@ impl FieldSettings { .get_ident() .ok_or(syn::Error::new_spanned(path, "Can't parse group"))?; - if !self.groups.insert(group_name.to_string()) { + if !self.groups.insert(group_name.clone()) { return Err(syn::Error::new_spanned( &expr, "Multiple adds to the same group", @@ -451,7 +266,10 @@ impl FieldSettings { .. }) = &expr { - if !self.groups.insert(lit.value()) { + if !self + .groups + .insert(syn::Ident::new(lit.value().as_str(), lit.span())) + { return Err(syn::Error::new_spanned( &expr, "Multiple adds to the same group", diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group_info.rs index 8e36a55..ffdbc16 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group_info.rs @@ -1,6 +1,4 @@ -use std::hash::Hash; - -use quote::format_ident; +use std::{collections::HashSet, hash::Hash}; use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; @@ -8,7 +6,7 @@ use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; #[derive(Debug, Clone)] pub struct GroupInfo { name: syn::Ident, - member_count: usize, + associated_indices: HashSet, group_type: GroupType, } @@ -26,7 +24,7 @@ impl GroupInfo { pub fn new(name: syn::Ident, group_type: GroupType) -> Self { GroupInfo { name, - member_count: 0, + associated_indices: HashSet::new(), group_type, } } @@ -45,28 +43,12 @@ impl GroupInfo { } } - /// Retrieves the current member count of the group. - pub fn member_count(&self) -> usize { - self.member_count - } - - /// Increments the member count and returns the next available index. - pub fn next_index(&mut self) -> usize { - self.member_count += 1; - self.member_count - 1 + pub fn associate(&mut self, index: usize) -> bool { + self.associated_indices.insert(index) } - /// Generates a partial constant identifier for the group at the given index. - /// - /// # Arguments - /// - /// - `index`: The index for which to generate the partial constant identifier. - /// - /// # Returns - /// - /// A `syn::Ident` representing the partial constant identifier. - pub fn partial_const_ident(&self, index: usize) -> syn::Ident { - format_ident!("{}_{}", &self.name.to_string().to_ascii_uppercase(), index) + pub fn indices(&self) -> &HashSet { + &self.associated_indices } /// Retrieves the function symbol associated with the group type. diff --git a/const_typed_builder_derive/src/info/mod.rs b/const_typed_builder_derive/src/info/mod.rs index bd5864a..c7e77b1 100644 --- a/const_typed_builder_derive/src/info/mod.rs +++ b/const_typed_builder_derive/src/info/mod.rs @@ -2,6 +2,6 @@ mod field_info; mod group_info; mod struct_info; -pub use field_info::FieldInfo; +pub use field_info::{FieldInfo, FieldKind}; pub use group_info::{GroupInfo, GroupType}; pub use struct_info::StructInfo; diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 211c21e..73556e4 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use super::field_info::{FieldInfo, FieldSettings}; use super::group_info::{GroupInfo, GroupType}; @@ -23,6 +23,7 @@ pub struct StructInfo<'a> { builder_ident: syn::Ident, /// The identifier of the generated data struct. data_ident: syn::Ident, + _mandatory_indices: HashSet, /// A map of group names to their respective `GroupInfo`. groups: HashMap, /// A collection of `FieldInfo` instances representing struct fields. @@ -61,7 +62,8 @@ impl<'a> StructInfo<'a> { let field_infos = fields .named .iter() - .map(|field| FieldInfo::new(field, &mut settings)) + .enumerate() + .map(|(index, field)| FieldInfo::new(field, &mut settings, index)) .collect::>>()?; let info = StructInfo { @@ -70,6 +72,7 @@ impl<'a> StructInfo<'a> { generics, builder_ident: format_ident!("{}{}", ident, settings.builder_suffix), data_ident: format_ident!("{}{}", ident, settings.data_suffix), + _mandatory_indices: settings.mandatory_indices, groups: settings.groups, field_infos, }; @@ -129,8 +132,7 @@ pub struct StructSettings { default_field_settings: FieldSettings, /// A map of group names to their respective `GroupInfo`. groups: HashMap, - /// The count of mandatory fields encountered. - mandatory_count: usize, + mandatory_indices: HashSet, } impl Default for StructSettings { @@ -140,7 +142,7 @@ impl Default for StructSettings { data_suffix: "Data".to_string(), default_field_settings: FieldSettings::new(), groups: HashMap::new(), - mandatory_count: 0, + mandatory_indices: HashSet::new(), } } } @@ -151,21 +153,12 @@ impl StructSettings { Default::default() } - /// Retrieves the next available mandatory field index. - pub fn next_mandatory(&mut self) -> usize { - self.mandatory_count += 1; - self.mandatory_count - 1 + pub fn add_mandatory_index(&mut self, index: usize) -> bool { + self.mandatory_indices.insert(index) } - /// Retrieves the next available group index for a given group name. - pub fn next_group_index(&mut self, group_name: &String) -> Option { - let res = self.groups.get_mut(group_name)?.next_index(); - Some(res) - } - - /// Retrieves a reference to a `GroupInfo` instance by group name. - pub fn group_by_name(&self, group_name: &String) -> Option<&GroupInfo> { - self.groups.get(group_name) + pub fn group_by_name_mut(&mut self, group_name: &String) -> Option<&mut GroupInfo> { + self.groups.get_mut(group_name) } /// Retrieves the default field settings. diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index 3b8413d..b3ddd58 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -13,9 +13,7 @@ type StreamResult = syn::Result; /// A type alias for the result of a vector of token streams. type VecStreamResult = syn::Result>; -/// A constant representing the mandatory prefix. -/// Used for making constants in the form -const MANDATORY_PREFIX: &str = "M"; +const CONST_IDENT_PREFIX: &str = "__BUILDER_CONST"; /// The `derive_builder` macro is used to automatically generate builder /// code for a struct. It takes a struct as input and generates a builder From ea70130f9ea85c4bc58c9dbbb77e457033a45571 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:06:57 +0200 Subject: [PATCH 05/65] Feature/optionally implement build for each combination of valid booleans (#21) * Make it work * Make brute force optional * Make sure users can opt into compiler solver * Fix lints, allow too many arguments in BuilderGenerator for now --- const_typed_builder_derive/Cargo.toml | 1 + .../src/generator/builder_generator.rs | 81 ++++++++++++++----- .../src/generator/generics_generator.rs | 25 +++++- .../src/generator/group_generator.rs | 20 +++++ .../src/generator/mod.rs | 1 + .../src/info/field_info.rs | 18 ++++- .../src/info/group_info.rs | 20 ++++- const_typed_builder_derive/src/info/mod.rs | 2 +- .../src/info/struct_info.rs | 48 +++++++++-- const_typed_builder_derive/src/symbol.rs | 5 ++ 10 files changed, 188 insertions(+), 33 deletions(-) diff --git a/const_typed_builder_derive/Cargo.toml b/const_typed_builder_derive/Cargo.toml index 3fa6dd6..005828d 100644 --- a/const_typed_builder_derive/Cargo.toml +++ b/const_typed_builder_derive/Cargo.toml @@ -17,6 +17,7 @@ syn = { version = "2.0", features = ["full", "extra-traits"] } quote = "1.0" proc-macro2 = "1.0" either = "1.9" +itertools = "0.11.0" [lib] proc-macro = true diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 2fe0d93..2c72f86 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -2,7 +2,7 @@ use super::{ field_generator::FieldGenerator, generics_generator::GenericsGenerator, group_generator::GroupGenerator, }; -use crate::{StreamResult, VecStreamResult}; +use crate::{info::SolveType, StreamResult, VecStreamResult}; use proc_macro2::TokenStream; use quote::quote; @@ -16,6 +16,7 @@ pub(super) struct BuilderGenerator<'a> { target_vis: &'a syn::Visibility, builder_name: &'a syn::Ident, data_name: &'a syn::Ident, + solve_type: SolveType, } impl<'a> BuilderGenerator<'a> { @@ -34,6 +35,7 @@ impl<'a> BuilderGenerator<'a> { /// # Returns /// /// A `BuilderGenerator` instance initialized with the provided information. + #[allow(clippy::too_many_arguments)] // TODO: remove? pub fn new( group_gen: GroupGenerator<'a>, field_gen: FieldGenerator<'a>, @@ -42,6 +44,7 @@ impl<'a> BuilderGenerator<'a> { target_vis: &'a syn::Visibility, builder_name: &'a syn::Ident, data_name: &'a syn::Ident, + solve_type: SolveType, ) -> Self { Self { group_gen, @@ -51,6 +54,7 @@ impl<'a> BuilderGenerator<'a> { target_vis, builder_name, data_name, + solve_type, } } @@ -131,30 +135,65 @@ impl<'a> BuilderGenerator<'a> { /// Generates the code for the `build` method implementation. fn generate_build_impl(&self) -> TokenStream { let builder_name = self.builder_name; - let impl_generics = self - .generics_gen - .builder_const_generic_group_partial_idents(); - let type_generics = self.generics_gen.builder_const_generic_idents_build(); - - let correctness_verifier = self.group_gen.builder_build_impl_correctness_verifier(); - let correctness_check = self.group_gen.builder_build_impl_correctness_check(); - let correctness_helper_fns = self.group_gen.builder_build_impl_correctness_helper_fns(); - let target_name = self.target_name; - let (_, target_type_generics, where_clause) = + let (impl_generics, target_type_generics, where_clause) = self.generics_gen.target_generics().split_for_impl(); - quote!( - impl #impl_generics #builder_name #type_generics #where_clause{ - #correctness_verifier - #correctness_helper_fns - - pub fn build(self) -> #target_name #target_type_generics { - #correctness_check - self.data.into() - } + match self.solve_type { + SolveType::BruteForce => { + let build_impls = + self.group_gen + .valid_groupident_combinations() + .map(|group_indices| { + let type_generics = self + .generics_gen + .builder_const_generic_idents_build(&group_indices); + + quote!( + impl #impl_generics #builder_name #type_generics #where_clause{ + + pub fn build(self) -> #target_name #target_type_generics { + self.data.into() + } + } + ) + }); + + quote!( + #(#build_impls)* + ) } - ) + SolveType::Compiler => { + let builder_name = self.builder_name; + let impl_generics = self + .generics_gen + .builder_const_generic_group_partial_idents(); + let type_generics = self + .generics_gen + .builder_const_generic_idents_build_unset_group(); + + let correctness_verifier = self.group_gen.builder_build_impl_correctness_verifier(); + let correctness_check = self.group_gen.builder_build_impl_correctness_check(); + let correctness_helper_fns = + self.group_gen.builder_build_impl_correctness_helper_fns(); + + let target_name = self.target_name; + let (_, target_type_generics, where_clause) = + self.generics_gen.target_generics().split_for_impl(); + + quote!( + impl #impl_generics #builder_name #type_generics #where_clause{ + #correctness_verifier + #correctness_helper_fns + + pub fn build(self) -> #target_name #target_type_generics { + #correctness_check + self.data.into() + } + } + ) + } + } } /// Generates the code for the setter methods of the builder. diff --git a/const_typed_builder_derive/src/generator/generics_generator.rs b/const_typed_builder_derive/src/generator/generics_generator.rs index 8763784..f5cc017 100644 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ b/const_typed_builder_derive/src/generator/generics_generator.rs @@ -98,12 +98,35 @@ impl<'a> GenericsGenerator<'a> { self.add_const_generics_for_impl(&mut all) } + /// Generates const generics for the builder `build` method and returns a token stream. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated const generics for the builder `build` method. + pub fn builder_const_generic_idents_build(&self, true_indices: &[usize]) -> TokenStream { + let mut all = self.fields.iter().filter_map(|field| match field.kind() { + FieldKind::Optional => None, + FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( + true, + proc_macro2::Span::call_site(), + ))), + FieldKind::Grouped if true_indices.contains(&field.index()) => Some(Either::Right( + syn::LitBool::new(true, proc_macro2::Span::call_site()), + )), + FieldKind::Grouped => Some(Either::Right(syn::LitBool::new( + false, + proc_macro2::Span::call_site(), + ))), + }); + self.add_const_generics_valued_for_type(&mut all) + } + // Generates const generics for the builder `build` method and returns a token stream. /// /// # Returns /// /// A `TokenStream` representing the generated const generics for the builder `build` method. - pub fn builder_const_generic_idents_build(&self) -> TokenStream { + pub fn builder_const_generic_idents_build_unset_group(&self) -> TokenStream { let mut all = self.fields.iter().filter_map(|field| match field.kind() { FieldKind::Optional => None, FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index b6b715f..db9be98 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -1,7 +1,10 @@ +use std::collections::BTreeSet; + use crate::{ info::{GroupInfo, GroupType}, CONST_IDENT_PREFIX, }; +use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -25,6 +28,23 @@ impl<'a> GroupGenerator<'a> { Self { groups } } + pub fn valid_groupident_combinations(&self) -> impl Iterator> + '_ { + let group_indices: BTreeSet = self + .groups + .iter() + .flat_map(|group| group.indices().clone()) + .collect(); + let powerset: Powerset> = + group_indices.into_iter().powerset(); + powerset.filter_map(|set| { + if self.groups.iter().all(|group| group.is_valid_with(&set)) { + Some(set) + } else { + None + } + }) + } + /// Generates correctness helper functions for group validation and returns a `TokenStream`. /// /// # Returns diff --git a/const_typed_builder_derive/src/generator/mod.rs b/const_typed_builder_derive/src/generator/mod.rs index 52ce890..1953f64 100644 --- a/const_typed_builder_derive/src/generator/mod.rs +++ b/const_typed_builder_derive/src/generator/mod.rs @@ -54,6 +54,7 @@ impl<'a> Generator<'a> { info.vis(), info.builder_name(), info.data_name(), + info.solve_type(), ), } } diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index 1c454bf..20511cf 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -21,7 +21,7 @@ pub struct FieldInfo<'a> { kind: FieldKind, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum FieldKind { Optional, Mandatory, @@ -119,6 +119,10 @@ impl<'a> FieldInfo<'a> { &self.kind } + pub fn index(&self) -> usize { + self.index + } + pub fn const_ident(&self) -> syn::Ident { format_ident!("{}{}", CONST_IDENT_PREFIX, self.index) } @@ -138,6 +142,18 @@ impl<'a> FieldInfo<'a> { } } +impl<'a> PartialOrd for FieldInfo<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + self.index.partial_cmp(&other.index) + } +} + +impl<'a> Ord for FieldInfo<'a> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.index.cmp(&other.index) + } +} + /// Represents settings for struct field generation. #[derive(Debug, Clone)] pub struct FieldSettings { diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group_info.rs index ffdbc16..dc1a8c9 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group_info.rs @@ -1,4 +1,4 @@ -use std::{collections::HashSet, hash::Hash}; +use std::{collections::BTreeSet, hash::Hash}; use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; @@ -6,7 +6,7 @@ use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; #[derive(Debug, Clone)] pub struct GroupInfo { name: syn::Ident, - associated_indices: HashSet, + associated_indices: BTreeSet, group_type: GroupType, } @@ -24,7 +24,7 @@ impl GroupInfo { pub fn new(name: syn::Ident, group_type: GroupType) -> Self { GroupInfo { name, - associated_indices: HashSet::new(), + associated_indices: BTreeSet::new(), group_type, } } @@ -47,7 +47,7 @@ impl GroupInfo { self.associated_indices.insert(index) } - pub fn indices(&self) -> &HashSet { + pub fn indices(&self) -> &BTreeSet { &self.associated_indices } @@ -64,6 +64,18 @@ impl GroupInfo { pub fn group_type(&self) -> &GroupType { &self.group_type } + + pub fn is_valid_with(&self, indices: &[usize]) -> bool { + let applicable_indices_count = self + .associated_indices + .intersection(&BTreeSet::from_iter(indices.iter().copied())) + .count(); + match self.group_type { + GroupType::Exact(count) => applicable_indices_count == count, + GroupType::AtLeast(count) => applicable_indices_count >= count, + GroupType::AtMost(count) => applicable_indices_count <= count, + } + } } impl Eq for GroupInfo {} diff --git a/const_typed_builder_derive/src/info/mod.rs b/const_typed_builder_derive/src/info/mod.rs index c7e77b1..17eed71 100644 --- a/const_typed_builder_derive/src/info/mod.rs +++ b/const_typed_builder_derive/src/info/mod.rs @@ -4,4 +4,4 @@ mod struct_info; pub use field_info::{FieldInfo, FieldKind}; pub use group_info::{GroupInfo, GroupType}; -pub use struct_info::StructInfo; +pub use struct_info::{SolveType, StructInfo}; diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 73556e4..ac10db9 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -1,15 +1,23 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap}; use super::field_info::{FieldInfo, FieldSettings}; use super::group_info::{GroupInfo, GroupType}; use quote::format_ident; use syn::Token; -use crate::symbol::{ASSUME_MANDATORY, AT_LEAST, AT_MOST, BUILDER, EXACT, GROUP, SINGLE}; +use crate::symbol::{ + ASSUME_MANDATORY, AT_LEAST, AT_MOST, BRUTE_FORCE, BUILDER, COMPILER, EXACT, GROUP, SINGLE, + SOLVER, +}; /// A type alias for a collection of `FieldInfo` instances. type FieldInfos<'a> = Vec>; +#[derive(Debug, Clone, Copy)] +pub enum SolveType { + BruteForce, + Compiler, +} /// Represents the information about a struct used for code generation. #[derive(Debug)] pub struct StructInfo<'a> { @@ -23,11 +31,12 @@ pub struct StructInfo<'a> { builder_ident: syn::Ident, /// The identifier of the generated data struct. data_ident: syn::Ident, - _mandatory_indices: HashSet, + _mandatory_indices: BTreeSet, /// A map of group names to their respective `GroupInfo`. groups: HashMap, /// A collection of `FieldInfo` instances representing struct fields. field_infos: FieldInfos<'a>, + solve_type: SolveType, } impl<'a> StructInfo<'a> { @@ -75,6 +84,7 @@ impl<'a> StructInfo<'a> { _mandatory_indices: settings.mandatory_indices, groups: settings.groups, field_infos, + solve_type: settings.solver_type, }; Ok(info) } else { @@ -119,6 +129,10 @@ impl<'a> StructInfo<'a> { pub fn groups(&self) -> &HashMap { &self.groups } + + pub fn solve_type(&self) -> SolveType { + self.solve_type + } } /// Represents settings for struct generation. @@ -132,7 +146,8 @@ pub struct StructSettings { default_field_settings: FieldSettings, /// A map of group names to their respective `GroupInfo`. groups: HashMap, - mandatory_indices: HashSet, + mandatory_indices: BTreeSet, + solver_type: SolveType, } impl Default for StructSettings { @@ -142,7 +157,8 @@ impl Default for StructSettings { data_suffix: "Data".to_string(), default_field_settings: FieldSettings::new(), groups: HashMap::new(), - mandatory_indices: HashSet::new(), + mandatory_indices: BTreeSet::new(), + solver_type: SolveType::BruteForce, } } } @@ -218,6 +234,28 @@ impl StructSettings { self.default_field_settings.mandatory = true; } } + if meta.path == SOLVER { + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + if let syn::Expr::Call(syn::ExprCall { func, .. }) = expr { + let solve_type = + if let syn::Expr::Path(syn::ExprPath { path, .. }) = func.as_ref() { + path.get_ident().ok_or_else(|| { + syn::Error::new_spanned(&func, "Can't parse group type") + }) + } else { + Err(syn::Error::new_spanned(func, "Can't find group type")) + }?; + match (&solve_type.to_string()).into() { + BRUTE_FORCE => self.solver_type = SolveType::BruteForce, + COMPILER => self.solver_type = SolveType::Compiler, + _ => todo!(), + } + } + } else { + self.default_field_settings.mandatory = true; + } + } Ok(()) }) } diff --git a/const_typed_builder_derive/src/symbol.rs b/const_typed_builder_derive/src/symbol.rs index 791b710..2ea5aa2 100644 --- a/const_typed_builder_derive/src/symbol.rs +++ b/const_typed_builder_derive/src/symbol.rs @@ -26,6 +26,11 @@ pub const PROPAGATE: Symbol = Symbol("propagate"); pub const ASSUME_MANDATORY: Symbol = Symbol("assume_mandatory"); /// Constant representing the "optional" symbol. pub const OPTIONAL: Symbol = Symbol("optional"); +/// Constant representing the "solver" symbol. +pub const SOLVER: Symbol = Symbol("solver"); + +pub const BRUTE_FORCE: Symbol = Symbol("brute_force"); +pub const COMPILER: Symbol = Symbol("compiler"); impl<'a> From<&'a String> for Symbol<'a> { fn from(value: &'a String) -> Self { From bd33b2dd9538e0ac8286c5f6fec0641917769dc0 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:26:42 +0200 Subject: [PATCH 06/65] Actually use different solver types (#22) * Actually use different solver types --- .../src/info/struct_info.rs | 20 +++++++++---------- const_typed_builder_test/src/lib.rs | 17 ++++++++++++++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index ac10db9..c2a688a 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -237,23 +237,21 @@ impl StructSettings { if meta.path == SOLVER { if meta.input.peek(Token![=]) { let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Call(syn::ExprCall { func, .. }) = expr { - let solve_type = - if let syn::Expr::Path(syn::ExprPath { path, .. }) = func.as_ref() { - path.get_ident().ok_or_else(|| { - syn::Error::new_spanned(&func, "Can't parse group type") - }) - } else { - Err(syn::Error::new_spanned(func, "Can't find group type")) - }?; + dbg!(&expr); + if let syn::Expr::Path(syn::ExprPath { path, .. }) = expr { + let solve_type = path + .get_ident() + .ok_or_else(|| syn::Error::new_spanned(&path, "Can't parse solver"))?; match (&solve_type.to_string()).into() { BRUTE_FORCE => self.solver_type = SolveType::BruteForce, COMPILER => self.solver_type = SolveType::Compiler, - _ => todo!(), + _ => Err(syn::Error::new_spanned(&path, "Unknown solver type"))?, } + } else { + Err(syn::Error::new_spanned(meta.path, "Can't parse solver"))? } } else { - self.default_field_settings.mandatory = true; + Err(syn::Error::new_spanned(meta.path, "Can't parse solver"))? } } Ok(()) diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index 50d9dbe..dc9a1bf 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -254,6 +254,23 @@ mod test { assert_eq!(expected, foo); } + #[test] + fn group_solver_compiler() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(baz = single)] + #[builder(solver = compiler)] + pub struct Foo { + #[builder(group = baz)] + bar: Option, + } + + let expected = Foo { + bar: Some("Hello world!".to_string()), + }; + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(expected, foo); + } + #[test] fn group_multiple_member() { #[derive(Debug, Default, PartialEq, Eq, Builder)] From 9d7820350f3757d5deb1a0dd0543bee91dfdf6a6 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 01:54:31 +0200 Subject: [PATCH 07/65] Feature/compilation fail tests trybuild (#15) * Add an compile_fail test for each passing test, and add a number to existing fail test. * Get trybuild to work with `cargo build` instead of just `cargo check` * Add Note to README.md * Remove unnecessary dbg comment --- README.md | 8 +- .../src/info/struct_info.rs | 1 - .../compile_fail/assume_mandatory_1.rs | 10 ++ .../compile_fail/assume_mandatory_1.stderr | 15 +++ .../assume_mandatory_explicit_optional_1.rs | 17 ++++ ...ssume_mandatory_explicit_optional_1.stderr | 19 ++++ .../compile_fail/group_1.rs | 11 +++ .../compile_fail/group_1.stderr | 8 ++ .../compile_fail/group_and_mandatory_1.rs | 15 +++ .../compile_fail/group_and_mandatory_1.stderr | 12 +++ .../compile_fail/group_and_mandatory_2.rs | 15 +++ .../compile_fail/group_and_mandatory_2.stderr | 12 +++ .../compile_fail/group_at_least_1.rs | 16 +++ .../compile_fail/group_at_least_1.stderr | 14 +++ .../compile_fail/group_at_least_2.rs | 16 +++ .../compile_fail/group_at_least_2.stderr | 8 ++ .../compile_fail/group_at_most_1.rs | 16 +++ .../compile_fail/group_at_most_1.stderr | 15 +++ .../compile_fail/group_multiple_member_1.rs | 13 +++ .../group_multiple_member_1.stderr | 12 +++ .../compile_fail/group_solver_compiler_1.rs | 13 +++ .../group_solver_compiler_1.stderr | 14 +++ ...ixed_mandatory.rs => mixed_mandatory_1.rs} | 0 ...datory.stderr => mixed_mandatory_1.stderr} | 2 +- .../multiple_generic_multiple_mandatory_1.rs | 11 +++ ...ltiple_generic_multiple_mandatory_1.stderr | 10 ++ ...generic_with_const_multiple_mandatory_1.rs | 11 +++ ...ric_with_const_multiple_mandatory_1.stderr | 17 ++++ ...e_mandatory.rs => multiple_mandatory_1.rs} | 0 ...ory.stderr => multiple_mandatory_1.stderr} | 2 +- ...ption_explicit.rs => option_explicit_1.rs} | 0 ...plicit.stderr => option_explicit_1.stderr} | 4 +- ...mandatory.rs => option_not_mandatory_1.rs} | 0 ...y.stderr => option_not_mandatory_1.stderr} | 4 +- ...l_mandatory.rs => optional_mandatory_1.rs} | 0 ...ory.stderr => optional_mandatory_1.stderr} | 4 +- ...ory_set.rs => optional_mandatory_set_1.rs} | 0 ...stderr => optional_mandatory_set_1.stderr} | 4 +- .../compile_fail/optional_propagate_1.rs | 16 +++ .../compile_fail/optional_propagate_1.stderr | 11 +++ .../compile_fail/optional_propagate_2.rs | 15 +++ .../compile_fail/optional_propagate_2.stderr | 12 +++ ...ple_mandatory.rs => simple_mandatory_1.rs} | 0 ...atory.stderr => simple_mandatory_1.stderr} | 2 +- .../single_generic_added_default_1.rs | 13 +++ .../single_generic_added_default_1.stderr | 11 +++ .../single_generic_multiple_mandatory_1.rs | 11 +++ ...single_generic_multiple_mandatory_1.stderr | 11 +++ ...eric_multiple_mandatory_added_default_1.rs | 13 +++ ..._multiple_mandatory_added_default_1.stderr | 11 +++ .../compile_fail/single_propagate_1.rs | 16 +++ .../compile_fail/single_propagate_1.stderr | 11 +++ .../compile_fail/single_propagate_2.rs | 16 +++ .../compile_fail/single_propagate_2.stderr | 8 ++ const_typed_builder_test/pass/empty_pass.rs | 4 + const_typed_builder_test/src/lib.rs | 98 +++---------------- 56 files changed, 531 insertions(+), 97 deletions(-) create mode 100644 const_typed_builder_test/compile_fail/assume_mandatory_1.rs create mode 100644 const_typed_builder_test/compile_fail/assume_mandatory_1.stderr create mode 100644 const_typed_builder_test/compile_fail/assume_mandatory_explicit_optional_1.rs create mode 100644 const_typed_builder_test/compile_fail/assume_mandatory_explicit_optional_1.stderr create mode 100644 const_typed_builder_test/compile_fail/group_1.rs create mode 100644 const_typed_builder_test/compile_fail/group_1.stderr create mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_1.rs create mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_1.stderr create mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_2.rs create mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_2.stderr create mode 100644 const_typed_builder_test/compile_fail/group_at_least_1.rs create mode 100644 const_typed_builder_test/compile_fail/group_at_least_1.stderr create mode 100644 const_typed_builder_test/compile_fail/group_at_least_2.rs create mode 100644 const_typed_builder_test/compile_fail/group_at_least_2.stderr create mode 100644 const_typed_builder_test/compile_fail/group_at_most_1.rs create mode 100644 const_typed_builder_test/compile_fail/group_at_most_1.stderr create mode 100644 const_typed_builder_test/compile_fail/group_multiple_member_1.rs create mode 100644 const_typed_builder_test/compile_fail/group_multiple_member_1.stderr create mode 100644 const_typed_builder_test/compile_fail/group_solver_compiler_1.rs create mode 100644 const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr rename const_typed_builder_test/compile_fail/{mixed_mandatory.rs => mixed_mandatory_1.rs} (100%) rename const_typed_builder_test/compile_fail/{mixed_mandatory.stderr => mixed_mandatory_1.stderr} (93%) create mode 100644 const_typed_builder_test/compile_fail/multiple_generic_multiple_mandatory_1.rs create mode 100644 const_typed_builder_test/compile_fail/multiple_generic_multiple_mandatory_1.stderr create mode 100644 const_typed_builder_test/compile_fail/multiple_generic_with_const_multiple_mandatory_1.rs create mode 100644 const_typed_builder_test/compile_fail/multiple_generic_with_const_multiple_mandatory_1.stderr rename const_typed_builder_test/compile_fail/{multiple_mandatory.rs => multiple_mandatory_1.rs} (100%) rename const_typed_builder_test/compile_fail/{multiple_mandatory.stderr => multiple_mandatory_1.stderr} (93%) rename const_typed_builder_test/compile_fail/{option_explicit.rs => option_explicit_1.rs} (100%) rename const_typed_builder_test/compile_fail/{option_explicit.stderr => option_explicit_1.stderr} (88%) rename const_typed_builder_test/compile_fail/{option_not_mandatory.rs => option_not_mandatory_1.rs} (100%) rename const_typed_builder_test/compile_fail/{option_not_mandatory.stderr => option_not_mandatory_1.stderr} (87%) rename const_typed_builder_test/compile_fail/{optional_mandatory.rs => optional_mandatory_1.rs} (100%) rename const_typed_builder_test/compile_fail/{optional_mandatory.stderr => optional_mandatory_1.stderr} (83%) rename const_typed_builder_test/compile_fail/{optional_mandatory_set.rs => optional_mandatory_set_1.rs} (100%) rename const_typed_builder_test/compile_fail/{optional_mandatory_set.stderr => optional_mandatory_set_1.stderr} (82%) create mode 100644 const_typed_builder_test/compile_fail/optional_propagate_1.rs create mode 100644 const_typed_builder_test/compile_fail/optional_propagate_1.stderr create mode 100644 const_typed_builder_test/compile_fail/optional_propagate_2.rs create mode 100644 const_typed_builder_test/compile_fail/optional_propagate_2.stderr rename const_typed_builder_test/compile_fail/{simple_mandatory.rs => simple_mandatory_1.rs} (100%) rename const_typed_builder_test/compile_fail/{simple_mandatory.stderr => simple_mandatory_1.stderr} (91%) create mode 100644 const_typed_builder_test/compile_fail/single_generic_added_default_1.rs create mode 100644 const_typed_builder_test/compile_fail/single_generic_added_default_1.stderr create mode 100644 const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_1.rs create mode 100644 const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_1.stderr create mode 100644 const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_added_default_1.rs create mode 100644 const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_added_default_1.stderr create mode 100644 const_typed_builder_test/compile_fail/single_propagate_1.rs create mode 100644 const_typed_builder_test/compile_fail/single_propagate_1.stderr create mode 100644 const_typed_builder_test/compile_fail/single_propagate_2.rs create mode 100644 const_typed_builder_test/compile_fail/single_propagate_2.stderr create mode 100644 const_typed_builder_test/pass/empty_pass.rs diff --git a/README.md b/README.md index 86ac152..d7a753f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # `Builder` Derive Macro Documentation The `Builder` derive macro is used to generate builder methods for structs in Rust. These builder methods allow you to construct instances of the struct by chaining method calls, providing a convenient and readable way to create complex objects with various configurations and compile-time validity checking. This documentation will provide an overview of how to use the `Builder` derive macro. @@ -86,3 +85,10 @@ impl Default for FooData { ``` ## Inspirations Builder macros have been done before, but not exactly what I needed for my use case. Also look into [derive_builder](https://crates.io/crates/derive_builder) and [typed-builder](https://crates.io/crates/typed-builder). Those projects are currently way more mature, but anyone willing to test this crate is currently a godsend. + +> [!NOTE] +> The current default implementation for checking the validity of grouped fields is `brute_force`. The problem is directly related to [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem), and the `brute_force` implementation currently has a $`O(2^g)`$ where $`g`$ is the amount of grouped variables. This is not a problem with a couple of fields, but it might impact compile time significantly with more fields. Future editions might improve on this. +> +> Although I haven't been able to recreate the issue yet, it seems that const values [aren't guaranteed to be evaluated at compile time](https://doc.rust-lang.org/reference/const_eval.html). This creates the issue that the group verification is not guaranteed to fail in its current implementation when the solver type is set to `compiler`. Users can opt in to the `compiler` solver, which might solve it quicker than `brute_force`, although this is not guaranteed. +> +> Anyone who would like to help, and add a SAT solver as a dependency (behind a feature flag) is welcome to do so. diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index c2a688a..b6b521c 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -237,7 +237,6 @@ impl StructSettings { if meta.path == SOLVER { if meta.input.peek(Token![=]) { let expr: syn::Expr = meta.value()?.parse()?; - dbg!(&expr); if let syn::Expr::Path(syn::ExprPath { path, .. }) = expr { let solve_type = path .get_ident() diff --git a/const_typed_builder_test/compile_fail/assume_mandatory_1.rs b/const_typed_builder_test/compile_fail/assume_mandatory_1.rs new file mode 100644 index 0000000..f3b50ef --- /dev/null +++ b/const_typed_builder_test/compile_fail/assume_mandatory_1.rs @@ -0,0 +1,10 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[builder(assume_mandatory)] + pub struct Foo { + bar: Option, + } + let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/assume_mandatory_1.stderr b/const_typed_builder_test/compile_fail/assume_mandatory_1.stderr new file mode 100644 index 0000000..b4480ae --- /dev/null +++ b/const_typed_builder_test/compile_fail/assume_mandatory_1.stderr @@ -0,0 +1,15 @@ +error[E0308]: mismatched types + --> ./compile_fail/assume_mandatory_1.rs:9:34 + | +9 | let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); + | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `String`, found `Option` + | | + | arguments to this method are incorrect + | + = note: expected struct `String` + found enum `Option` +note: method defined here + --> ./compile_fail/assume_mandatory_1.rs:7:9 + | +7 | bar: Option, + | ^^^--------------- diff --git a/const_typed_builder_test/compile_fail/assume_mandatory_explicit_optional_1.rs b/const_typed_builder_test/compile_fail/assume_mandatory_explicit_optional_1.rs new file mode 100644 index 0000000..953dfe9 --- /dev/null +++ b/const_typed_builder_test/compile_fail/assume_mandatory_explicit_optional_1.rs @@ -0,0 +1,17 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[builder(assume_mandatory)] + pub struct Foo { + bar: Option, + baz: Option, + #[builder(optional)] + quz: Option, + } + let foo = Foo::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!".to_string()) + .quz("Hello world!".to_string()) + .build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/assume_mandatory_explicit_optional_1.stderr b/const_typed_builder_test/compile_fail/assume_mandatory_explicit_optional_1.stderr new file mode 100644 index 0000000..40b5b21 --- /dev/null +++ b/const_typed_builder_test/compile_fail/assume_mandatory_explicit_optional_1.stderr @@ -0,0 +1,19 @@ +error[E0308]: mismatched types + --> ./compile_fail/assume_mandatory_explicit_optional_1.rs:15:14 + | +15 | .quz("Hello world!".to_string()) + | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Option`, found `String` + | | + | arguments to this method are incorrect + | + = note: expected enum `Option` + found struct `String` +note: method defined here + --> ./compile_fail/assume_mandatory_explicit_optional_1.rs:10:9 + | +10 | quz: Option, + | ^^^---------------- +help: try wrapping the expression in `Some` + | +15 | .quz(Some("Hello world!".to_string())) + | +++++ + diff --git a/const_typed_builder_test/compile_fail/group_1.rs b/const_typed_builder_test/compile_fail/group_1.rs new file mode 100644 index 0000000..eb5aa5a --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_1.rs @@ -0,0 +1,11 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(baz = at_least(2))] + pub struct Foo { + #[builder(group = baz)] + bar: Option, + } + let foo = Foo::builder().bar("Hello world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_1.stderr b/const_typed_builder_test/compile_fail/group_1.stderr new file mode 100644 index 0000000..7f13c2a --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_1.stderr @@ -0,0 +1,8 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_1.rs:10:62 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +10 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_1.rs b/const_typed_builder_test/compile_fail/group_and_mandatory_1.rs new file mode 100644 index 0000000..d92e476 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_1.rs @@ -0,0 +1,15 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = single)] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + qux: String, + } + + let _ = Foo::builder().baz("Hello".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_1.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_1.stderr new file mode 100644 index 0000000..ce092aa --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_1.stderr @@ -0,0 +1,12 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_and_mandatory_1.rs:14:53 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +14 | let _ = Foo::builder().baz("Hello".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` + | + = note: the method was found for + - `FooBuilder` + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_2.rs b/const_typed_builder_test/compile_fail/group_and_mandatory_2.rs new file mode 100644 index 0000000..70bb983 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_2.rs @@ -0,0 +1,15 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = single)] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + qux: String, + } + + let _ = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_2.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_2.stderr new file mode 100644 index 0000000..1301a06 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_2.stderr @@ -0,0 +1,12 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_and_mandatory_2.rs:14:104 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +14 | let _ = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` + | + = note: the method was found for + - `FooBuilder` + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/group_at_least_1.rs b/const_typed_builder_test/compile_fail/group_at_least_1.rs new file mode 100644 index 0000000..7bf5ff6 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_at_least_1.rs @@ -0,0 +1,16 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = at_least(2))] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + #[builder(group = quz)] + qux: Option, + } + + let foo = Foo::builder().baz("Hello".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_at_least_1.stderr b/const_typed_builder_test/compile_fail/group_at_least_1.stderr new file mode 100644 index 0000000..4969fe0 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_at_least_1.stderr @@ -0,0 +1,14 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_at_least_1.rs:15:55 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +15 | let foo = Foo::builder().baz("Hello".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` + | + = note: the method was found for + - `FooBuilder` + - `FooBuilder` + - `FooBuilder` + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/group_at_least_2.rs b/const_typed_builder_test/compile_fail/group_at_least_2.rs new file mode 100644 index 0000000..d926e91 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_at_least_2.rs @@ -0,0 +1,16 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = at_least(2))] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + #[builder(group = quz)] + qux: Option, + } + + let foo = Foo::builder().bar("Hello".to_string()).bar("Hello".to_string()).qux("world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_at_least_2.stderr b/const_typed_builder_test/compile_fail/group_at_least_2.stderr new file mode 100644 index 0000000..752f98b --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_at_least_2.stderr @@ -0,0 +1,8 @@ +error[E0599]: no method named `bar` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_at_least_2.rs:15:55 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `bar` not found for this struct +... +15 | let foo = Foo::builder().bar("Hello".to_string()).bar("Hello".to_string()).qux("world!".to_string()).build(); + | ^^^ help: there is a method with a similar name: `baz` diff --git a/const_typed_builder_test/compile_fail/group_at_most_1.rs b/const_typed_builder_test/compile_fail/group_at_most_1.rs new file mode 100644 index 0000000..4384edb --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_at_most_1.rs @@ -0,0 +1,16 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = at_most(2))] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + baz: Option, + #[builder(group = quz)] + qux: Option, + } + + let foo = Foo::builder().qux("!".to_string()).baz("world".to_string()).bar("Hello".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_at_most_1.stderr b/const_typed_builder_test/compile_fail/group_at_most_1.stderr new file mode 100644 index 0000000..c0b4027 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_at_most_1.stderr @@ -0,0 +1,15 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_at_most_1.rs:15:102 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +15 | let foo = Foo::builder().qux("!".to_string()).baz("world".to_string()).bar("Hello".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` + | + = note: the method was found for + - `FooBuilder` + - `FooBuilder` + - `FooBuilder` + - `FooBuilder` + and 3 more types diff --git a/const_typed_builder_test/compile_fail/group_multiple_member_1.rs b/const_typed_builder_test/compile_fail/group_multiple_member_1.rs new file mode 100644 index 0000000..1cd2916 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_multiple_member_1.rs @@ -0,0 +1,13 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(baz = single)] + pub struct Foo { + #[builder(group = baz)] + bar: Option, + #[builder(group = baz)] + qux: Option, + } + _ = Foo::builder().bar("Hello world!".to_string()).qux("Hello world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_multiple_member_1.stderr b/const_typed_builder_test/compile_fail/group_multiple_member_1.stderr new file mode 100644 index 0000000..2416003 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_multiple_member_1.stderr @@ -0,0 +1,12 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_multiple_member_1.rs:12:88 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +12 | _ = Foo::builder().bar("Hello world!".to_string()).qux("Hello world!".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` + | + = note: the method was found for + - `FooBuilder` + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs b/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs new file mode 100644 index 0000000..bbf4cea --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs @@ -0,0 +1,13 @@ +// This code is not compiling but trybuild thinks it does +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(baz = at_least(2))] + #[build(solver = compiler)] + pub struct Foo { + #[builder(group = baz)] + bar: Option, + } + let foo = Foo::builder().bar("Hello world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr b/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr new file mode 100644 index 0000000..6e623b9 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr @@ -0,0 +1,14 @@ +error: cannot find attribute `build` in this scope + --> ./compile_fail/group_solver_compiler_1.rs:7:7 + | +7 | #[build(solver = compiler)] + | ^^^^^ + +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_solver_compiler_1.rs:12:62 + | +5 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +12 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/mixed_mandatory.rs b/const_typed_builder_test/compile_fail/mixed_mandatory_1.rs similarity index 100% rename from const_typed_builder_test/compile_fail/mixed_mandatory.rs rename to const_typed_builder_test/compile_fail/mixed_mandatory_1.rs diff --git a/const_typed_builder_test/compile_fail/mixed_mandatory.stderr b/const_typed_builder_test/compile_fail/mixed_mandatory_1.stderr similarity index 93% rename from const_typed_builder_test/compile_fail/mixed_mandatory.stderr rename to const_typed_builder_test/compile_fail/mixed_mandatory_1.stderr index 6d2c801..07e1c79 100644 --- a/const_typed_builder_test/compile_fail/mixed_mandatory.stderr +++ b/const_typed_builder_test/compile_fail/mixed_mandatory_1.stderr @@ -1,5 +1,5 @@ error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope - --> ./compile_fail/mixed_mandatory.rs:16:10 + --> ./compile_fail/mixed_mandatory_1.rs:16:10 | 4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] | ------- method `build` not found for this struct diff --git a/const_typed_builder_test/compile_fail/multiple_generic_multiple_mandatory_1.rs b/const_typed_builder_test/compile_fail/multiple_generic_multiple_mandatory_1.rs new file mode 100644 index 0000000..7e63aff --- /dev/null +++ b/const_typed_builder_test/compile_fail/multiple_generic_multiple_mandatory_1.rs @@ -0,0 +1,11 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: A, + baz: B, + } + + let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/multiple_generic_multiple_mandatory_1.stderr b/const_typed_builder_test/compile_fail/multiple_generic_multiple_mandatory_1.stderr new file mode 100644 index 0000000..b16d763 --- /dev/null +++ b/const_typed_builder_test/compile_fail/multiple_generic_multiple_mandatory_1.stderr @@ -0,0 +1,10 @@ +error[E0308]: mismatched types + --> ./compile_fail/multiple_generic_multiple_mandatory_1.rs:10:36 + | +10 | let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!".to_string()).build(); + | ------------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Foo`, found `Foo` + | | + | expected due to this + | + = note: expected struct `Foo<_, usize>` + found struct `Foo<_, String>` diff --git a/const_typed_builder_test/compile_fail/multiple_generic_with_const_multiple_mandatory_1.rs b/const_typed_builder_test/compile_fail/multiple_generic_with_const_multiple_mandatory_1.rs new file mode 100644 index 0000000..bdf9a27 --- /dev/null +++ b/const_typed_builder_test/compile_fail/multiple_generic_with_const_multiple_mandatory_1.rs @@ -0,0 +1,11 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: A, + baz: B, + } + + let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/multiple_generic_with_const_multiple_mandatory_1.stderr b/const_typed_builder_test/compile_fail/multiple_generic_with_const_multiple_mandatory_1.stderr new file mode 100644 index 0000000..008cb6f --- /dev/null +++ b/const_typed_builder_test/compile_fail/multiple_generic_with_const_multiple_mandatory_1.stderr @@ -0,0 +1,17 @@ +error[E0107]: struct takes 3 generic arguments but 2 generic arguments were supplied + --> ./compile_fail/multiple_generic_with_const_multiple_mandatory_1.rs:10:14 + | +10 | let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); + | ^^^ ------ ---- supplied 2 generic arguments + | | + | expected 3 generic arguments + | +note: struct defined here, with 3 generic parameters: `A`, `B`, `C` + --> ./compile_fail/multiple_generic_with_const_multiple_mandatory_1.rs:5:16 + | +5 | pub struct Foo { + | ^^^ - - -------------- +help: add missing generic argument + | +10 | let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); + | +++ diff --git a/const_typed_builder_test/compile_fail/multiple_mandatory.rs b/const_typed_builder_test/compile_fail/multiple_mandatory_1.rs similarity index 100% rename from const_typed_builder_test/compile_fail/multiple_mandatory.rs rename to const_typed_builder_test/compile_fail/multiple_mandatory_1.rs diff --git a/const_typed_builder_test/compile_fail/multiple_mandatory.stderr b/const_typed_builder_test/compile_fail/multiple_mandatory_1.stderr similarity index 93% rename from const_typed_builder_test/compile_fail/multiple_mandatory.stderr rename to const_typed_builder_test/compile_fail/multiple_mandatory_1.stderr index c40da37..87c83f4 100644 --- a/const_typed_builder_test/compile_fail/multiple_mandatory.stderr +++ b/const_typed_builder_test/compile_fail/multiple_mandatory_1.stderr @@ -1,5 +1,5 @@ error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope - --> ./compile_fail/multiple_mandatory.rs:16:10 + --> ./compile_fail/multiple_mandatory_1.rs:16:10 | 4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] | ------- method `build` not found for this struct diff --git a/const_typed_builder_test/compile_fail/option_explicit.rs b/const_typed_builder_test/compile_fail/option_explicit_1.rs similarity index 100% rename from const_typed_builder_test/compile_fail/option_explicit.rs rename to const_typed_builder_test/compile_fail/option_explicit_1.rs diff --git a/const_typed_builder_test/compile_fail/option_explicit.stderr b/const_typed_builder_test/compile_fail/option_explicit_1.stderr similarity index 88% rename from const_typed_builder_test/compile_fail/option_explicit.stderr rename to const_typed_builder_test/compile_fail/option_explicit_1.stderr index a94303c..a8ff17e 100644 --- a/const_typed_builder_test/compile_fail/option_explicit.stderr +++ b/const_typed_builder_test/compile_fail/option_explicit_1.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> ./compile_fail/option_explicit.rs:9:34 + --> ./compile_fail/option_explicit_1.rs:9:34 | 9 | let foo = Foo::builder().bar("Hello world!".to_string()); | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Option`, found `String` @@ -9,7 +9,7 @@ error[E0308]: mismatched types = note: expected enum `Option` found struct `String` note: method defined here - --> ./compile_fail/option_explicit.rs:6:9 + --> ./compile_fail/option_explicit_1.rs:6:9 | 6 | bar: std::option::Option, | ^^^----------------------------- diff --git a/const_typed_builder_test/compile_fail/option_not_mandatory.rs b/const_typed_builder_test/compile_fail/option_not_mandatory_1.rs similarity index 100% rename from const_typed_builder_test/compile_fail/option_not_mandatory.rs rename to const_typed_builder_test/compile_fail/option_not_mandatory_1.rs diff --git a/const_typed_builder_test/compile_fail/option_not_mandatory.stderr b/const_typed_builder_test/compile_fail/option_not_mandatory_1.stderr similarity index 87% rename from const_typed_builder_test/compile_fail/option_not_mandatory.stderr rename to const_typed_builder_test/compile_fail/option_not_mandatory_1.stderr index 0cdcb57..732df07 100644 --- a/const_typed_builder_test/compile_fail/option_not_mandatory.stderr +++ b/const_typed_builder_test/compile_fail/option_not_mandatory_1.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> ./compile_fail/option_not_mandatory.rs:8:34 + --> ./compile_fail/option_not_mandatory_1.rs:8:34 | 8 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Option`, found `String` @@ -9,7 +9,7 @@ error[E0308]: mismatched types = note: expected enum `Option` found struct `String` note: method defined here - --> ./compile_fail/option_not_mandatory.rs:6:9 + --> ./compile_fail/option_not_mandatory_1.rs:6:9 | 6 | bar: Option, | ^^^---------------- diff --git a/const_typed_builder_test/compile_fail/optional_mandatory.rs b/const_typed_builder_test/compile_fail/optional_mandatory_1.rs similarity index 100% rename from const_typed_builder_test/compile_fail/optional_mandatory.rs rename to const_typed_builder_test/compile_fail/optional_mandatory_1.rs diff --git a/const_typed_builder_test/compile_fail/optional_mandatory.stderr b/const_typed_builder_test/compile_fail/optional_mandatory_1.stderr similarity index 83% rename from const_typed_builder_test/compile_fail/optional_mandatory.stderr rename to const_typed_builder_test/compile_fail/optional_mandatory_1.stderr index 219615d..35310b1 100644 --- a/const_typed_builder_test/compile_fail/optional_mandatory.stderr +++ b/const_typed_builder_test/compile_fail/optional_mandatory_1.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> ./compile_fail/optional_mandatory.rs:10:34 + --> ./compile_fail/optional_mandatory_1.rs:10:34 | 10 | let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `String`, found `Option` @@ -9,7 +9,7 @@ error[E0308]: mismatched types = note: expected struct `String` found enum `Option` note: method defined here - --> ./compile_fail/optional_mandatory.rs:7:9 + --> ./compile_fail/optional_mandatory_1.rs:7:9 | 7 | bar: Option, | ^^^--------------- diff --git a/const_typed_builder_test/compile_fail/optional_mandatory_set.rs b/const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs similarity index 100% rename from const_typed_builder_test/compile_fail/optional_mandatory_set.rs rename to const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs diff --git a/const_typed_builder_test/compile_fail/optional_mandatory_set.stderr b/const_typed_builder_test/compile_fail/optional_mandatory_set_1.stderr similarity index 82% rename from const_typed_builder_test/compile_fail/optional_mandatory_set.stderr rename to const_typed_builder_test/compile_fail/optional_mandatory_set_1.stderr index d9d23ad..e2f450e 100644 --- a/const_typed_builder_test/compile_fail/optional_mandatory_set.stderr +++ b/const_typed_builder_test/compile_fail/optional_mandatory_set_1.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> ./compile_fail/optional_mandatory_set.rs:10:34 + --> ./compile_fail/optional_mandatory_set_1.rs:10:34 | 10 | let foo = Foo::builder().bar(Some("Hello world!".to_string())).build(); | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `String`, found `Option` @@ -9,7 +9,7 @@ error[E0308]: mismatched types = note: expected struct `String` found enum `Option` note: method defined here - --> ./compile_fail/optional_mandatory_set.rs:7:9 + --> ./compile_fail/optional_mandatory_set_1.rs:7:9 | 7 | bar: Option, | ^^^--------------- diff --git a/const_typed_builder_test/compile_fail/optional_propagate_1.rs b/const_typed_builder_test/compile_fail/optional_propagate_1.rs new file mode 100644 index 0000000..690a96b --- /dev/null +++ b/const_typed_builder_test/compile_fail/optional_propagate_1.rs @@ -0,0 +1,16 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(propagate)] + bar: Option, + } + + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Bar { + baz: String, + } + + let foo = Foo::builder().bar(|builder| builder.build() ).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/optional_propagate_1.stderr b/const_typed_builder_test/compile_fail/optional_propagate_1.stderr new file mode 100644 index 0000000..90d788b --- /dev/null +++ b/const_typed_builder_test/compile_fail/optional_propagate_1.stderr @@ -0,0 +1,11 @@ +error[E0599]: no method named `build` found for struct `BarBuilder` in the current scope + --> ./compile_fail/optional_propagate_1.rs:15:52 + | +10 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +15 | let foo = Foo::builder().bar(|builder| builder.build() ).build(); + | ^^^^^ method not found in `BarBuilder` + | + = note: the method was found for + - `BarBuilder` diff --git a/const_typed_builder_test/compile_fail/optional_propagate_2.rs b/const_typed_builder_test/compile_fail/optional_propagate_2.rs new file mode 100644 index 0000000..b067f6a --- /dev/null +++ b/const_typed_builder_test/compile_fail/optional_propagate_2.rs @@ -0,0 +1,15 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(propagate)] + bar: Option, + } + + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Bar { + baz: String, + } + let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/optional_propagate_2.stderr b/const_typed_builder_test/compile_fail/optional_propagate_2.stderr new file mode 100644 index 0000000..fb00e62 --- /dev/null +++ b/const_typed_builder_test/compile_fail/optional_propagate_2.stderr @@ -0,0 +1,12 @@ +error[E0308]: mismatched types + --> ./compile_fail/optional_propagate_2.rs:14:44 + | +14 | let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build()).build(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Option`, found `Bar` + | + = note: expected enum `Option` + found struct `Bar` +help: try wrapping the expression in `Some` + | +14 | let foo = Foo::builder().bar(|builder| Some(builder.baz("Hello world!".to_string()).build())).build(); + | +++++ + diff --git a/const_typed_builder_test/compile_fail/simple_mandatory.rs b/const_typed_builder_test/compile_fail/simple_mandatory_1.rs similarity index 100% rename from const_typed_builder_test/compile_fail/simple_mandatory.rs rename to const_typed_builder_test/compile_fail/simple_mandatory_1.rs diff --git a/const_typed_builder_test/compile_fail/simple_mandatory.stderr b/const_typed_builder_test/compile_fail/simple_mandatory_1.stderr similarity index 91% rename from const_typed_builder_test/compile_fail/simple_mandatory.stderr rename to const_typed_builder_test/compile_fail/simple_mandatory_1.stderr index 0ccc494..645e0d9 100644 --- a/const_typed_builder_test/compile_fail/simple_mandatory.stderr +++ b/const_typed_builder_test/compile_fail/simple_mandatory_1.stderr @@ -1,5 +1,5 @@ error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope - --> ./compile_fail/simple_mandatory.rs:10:10 + --> ./compile_fail/simple_mandatory_1.rs:10:10 | 4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] | ------- method `build` not found for this struct diff --git a/const_typed_builder_test/compile_fail/single_generic_added_default_1.rs b/const_typed_builder_test/compile_fail/single_generic_added_default_1.rs new file mode 100644 index 0000000..cd9a7b2 --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_generic_added_default_1.rs @@ -0,0 +1,13 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo + where + A: Default, + { + bar: A, + } + + let foo: Foo> = Foo::builder().build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/single_generic_added_default_1.stderr b/const_typed_builder_test/compile_fail/single_generic_added_default_1.stderr new file mode 100644 index 0000000..c58f0fe --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_generic_added_default_1.stderr @@ -0,0 +1,11 @@ +error[E0599]: no method named `build` found for struct `FooBuilder<_, false>` in the current scope + --> ./compile_fail/single_generic_added_default_1.rs:12:51 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +12 | let foo: Foo> = Foo::builder().build(); + | ^^^^^ method not found in `FooBuilder<_, false>` + | + = note: the method was found for + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_1.rs b/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_1.rs new file mode 100644 index 0000000..ec833b3 --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_1.rs @@ -0,0 +1,11 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + bar: A, + baz: A, + } + + let foo = Foo::builder().bar("Hello world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_1.stderr b/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_1.stderr new file mode 100644 index 0000000..3a0a558 --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_1.stderr @@ -0,0 +1,11 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/single_generic_multiple_mandatory_1.rs:10:62 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +10 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` + | + = note: the method was found for + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_added_default_1.rs b/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_added_default_1.rs new file mode 100644 index 0000000..ddc90d4 --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_added_default_1.rs @@ -0,0 +1,13 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo + where + A: Default, + { + bar: A, + baz: A, + } + let foo = Foo::builder().build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_added_default_1.stderr b/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_added_default_1.stderr new file mode 100644 index 0000000..b4dc0ec --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_generic_multiple_mandatory_added_default_1.stderr @@ -0,0 +1,11 @@ +error[E0599]: no method named `build` found for struct `FooBuilder<_, false, false>` in the current scope + --> ./compile_fail/single_generic_multiple_mandatory_added_default_1.rs:12:30 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +12 | let foo = Foo::builder().build(); + | ^^^^^ method not found in `FooBuilder<_, false, false>` + | + = note: the method was found for + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/single_propagate_1.rs b/const_typed_builder_test/compile_fail/single_propagate_1.rs new file mode 100644 index 0000000..41101e1 --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_propagate_1.rs @@ -0,0 +1,16 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(propagate)] + bar: Bar, + } + + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Bar { + baz: String, + } + + let foo = Foo::builder().bar(|builder| builder.build() ).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/single_propagate_1.stderr b/const_typed_builder_test/compile_fail/single_propagate_1.stderr new file mode 100644 index 0000000..0af1434 --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_propagate_1.stderr @@ -0,0 +1,11 @@ +error[E0599]: no method named `build` found for struct `BarBuilder` in the current scope + --> ./compile_fail/single_propagate_1.rs:15:52 + | +10 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +15 | let foo = Foo::builder().bar(|builder| builder.build() ).build(); + | ^^^^^ method not found in `BarBuilder` + | + = note: the method was found for + - `BarBuilder` diff --git a/const_typed_builder_test/compile_fail/single_propagate_2.rs b/const_typed_builder_test/compile_fail/single_propagate_2.rs new file mode 100644 index 0000000..71269ab --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_propagate_2.rs @@ -0,0 +1,16 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + pub struct Foo { + #[builder(propagate)] + bar: Bar, + } + + #[derive(Debug, Default, PartialEq, Eq)] + pub struct Bar { + baz: String, + } + + let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build() ).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/single_propagate_2.stderr b/const_typed_builder_test/compile_fail/single_propagate_2.stderr new file mode 100644 index 0000000..62c24d3 --- /dev/null +++ b/const_typed_builder_test/compile_fail/single_propagate_2.stderr @@ -0,0 +1,8 @@ +error[E0277]: the trait bound `Bar: const_typed_builder::Builder` is not satisfied + --> ./compile_fail/single_propagate_2.rs:4:45 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ^^^^^^^ the trait `const_typed_builder::Builder` is not implemented for `Bar` + | + = help: the trait `const_typed_builder::Builder` is implemented for `Foo` + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/const_typed_builder_test/pass/empty_pass.rs b/const_typed_builder_test/pass/empty_pass.rs new file mode 100644 index 0000000..5305933 --- /dev/null +++ b/const_typed_builder_test/pass/empty_pass.rs @@ -0,0 +1,4 @@ +fn main() { + // intentionally empty + // Reason: https://github.com/dtolnay/trybuild/issues/242#issuecomment-1721554914 +} \ No newline at end of file diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index dc9a1bf..9dc6da0 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -5,6 +5,7 @@ mod test { #[test] fn compile_fail_tests() { let test_cases = trybuild::TestCases::new(); + test_cases.pass("./pass/empty_pass.rs"); let test_dir = std::fs::read_dir("./compile_fail").expect("Can't find test directory"); test_dir.for_each(|entry| { let entry = entry.expect("Can't find test entry"); @@ -26,8 +27,6 @@ mod test { }; let foo = Foo::builder().bar("Hello world!".to_string()).build(); assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); } #[test] @@ -295,14 +294,6 @@ mod test { }; let foo = Foo::builder().qux("Hello world!".to_string()).build(); assert_eq!(expected, foo); - - // // EXPECT: DOESNT RUN - // let expected = Foo { - // bar: Some("Hello world!".to_string()), - // qux: Some("Hello world!".to_string()), - // }; - // let foo = Foo::builder().bar("Hello world!".to_string()).qux("Hello world!".to_string()).build(); - // assert_eq!(expected, foo); } #[test] @@ -340,9 +331,6 @@ mod test { .baz("Hello".to_string()) .build(); assert_eq!(expected, foo); - - // let foo = Foo::builder().baz("Hello".to_string()).build(); - // let foo = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); } #[test] @@ -382,9 +370,7 @@ mod test { .build(); assert_eq!(expected, foo); - // let foo = Foo::builder().baz("Hello".to_string()).build(); - // let foo = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); - + // FIXME: Should fail or warn #[derive(Debug, Default, PartialEq, Eq, Builder)] #[group(quz = single)] pub struct Nope { @@ -447,9 +433,6 @@ mod test { .bar("Hello".to_string()) .build(); assert_eq!(expected, foo); - - // let foo = Foo::builder().baz("Hello".to_string()).build(); - // let foo = Foo::builder().bar("Hello".to_string()).bar("Hello".to_string()).qux("world!".to_string()).build(); } #[test] @@ -496,17 +479,6 @@ mod test { }; let foo = Foo::builder().baz("Hello world!".to_string()).build(); assert_eq!(expected, foo); - - // let expected = Foo { - // bar: Some("Hello".to_string()), - // baz: Some("world".to_string()), - // qux: Some("!".to_string()), - // }; - - // let foo = Foo::builder().qux("!".to_string()).baz("world".to_string()).bar("Hello".to_string()).build(); - // assert_eq!(expected, foo); - - // let foo = Foo::builder().bar("Hello".to_string()).bar("Hello".to_string()).qux("world!".to_string()).build(); } #[test] @@ -557,8 +529,6 @@ mod test { .baz("Hello world!".to_string()) .build(); assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); } #[test] @@ -583,9 +553,15 @@ mod test { .bar("Hello world!".to_string()) .baz("Hello world!".to_string()) .build(); + assert_eq!(expected, foo); - // let foo = Foo::builder().build(); + let foo = Foo::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!".to_string()) + .build(); + + assert_eq!(expected, foo); } #[test] @@ -606,10 +582,11 @@ mod test { .build(); assert_eq!(expected, foo); - // let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); - // assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); + let foo: Foo = Foo::builder() + .bar("Hello world!".to_string()) + .baz("Hello world!") + .build(); + assert_eq!(expected, foo); } #[test] @@ -629,11 +606,6 @@ mod test { .baz("Hello world!") .build(); assert_eq!(expected, foo); - - // let foo: Foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!").build(); - // assert_eq!(expected, foo); - - // let foo = Foo::builder().build(); } #[test] @@ -658,27 +630,6 @@ mod test { .bar(|builder| builder.baz("Hello world!".to_string()).build()) .build(); assert_eq!(expected, foo); - - // let foo = Foo::builder().bar(|builder| builder.build() ).build(); - - // #[derive(Debug, Default, PartialEq, Eq, Builder)] - // pub struct Foo { - // #[builder(propagate)] - // bar: Bar, - // } - - // #[derive(Debug, Default, PartialEq, Eq, Builder)] - // pub struct Bar { - // baz: String, - // } - - // let expected = Foo { - // bar: Bar { - // baz: "Hello world!".to_string(), - // } - // }; - // let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build() ).build(); - // assert_eq!(expected, foo); } #[test] @@ -703,26 +654,5 @@ mod test { .bar(|builder| Some(builder.baz("Hello world!".to_string()).build())) .build(); assert_eq!(expected, foo); - - // let foo = Foo::builder().bar(|builder| builder.build() ).build(); - - // #[derive(Debug, Default, PartialEq, Eq, Builder)] - // pub struct Foo { - // #[builder(propagate)] - // bar: Bar, - // } - - // #[derive(Debug, Default, PartialEq, Eq, Builder)] - // pub struct Bar { - // baz: String, - // } - - // let expected = Foo { - // bar: Bar { - // baz: "Hello world!".to_string(), - // } - // }; - // let foo = Foo::builder().bar(|builder| builder.baz("Hello world!".to_string()).build() ).build(); - // assert_eq!(expected, foo); } } From c349bc8eaffc3b4018d71ea980d904642f24a936 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 03:20:40 +0200 Subject: [PATCH 08/65] feature/get-ready-for-020 (#24) * Bump version * Update readme * Update documentation * Forgot `cargo fmt` before publish. Minor changes --- Cargo.toml | 4 +- README.md | 21 ++++--- .../src/generator/builder_generator.rs | 1 + .../src/generator/field_generator.rs | 7 ++- .../src/generator/generics_generator.rs | 11 +++- .../src/generator/group_generator.rs | 6 ++ .../src/info/field_info.rs | 53 ++++++++++++++++- .../src/info/group_info.rs | 10 ++-- .../src/info/struct_info.rs | 57 ++++++++++++++++++- const_typed_builder_derive/src/symbol.rs | 25 ++++---- const_typed_builder_test/Cargo.toml | 2 +- src/lib.rs | 7 ++- 12 files changed, 164 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 41a6980..996dfdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "./const_typed_builder_derive", "./const_typed_builder_test"] [workspace.package] description = "Compile-time type-checked builder derive using const generics" -version = "0.1.1" +version = "0.2.0" authors = ["Koenichiwa "] repository = "https://github.com/koenichiwa/const_typed_builder/" edition = "2021" @@ -27,7 +27,7 @@ readme.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -const_typed_builder_derive = { path = "const_typed_builder_derive", version = "=0.1.1" } +const_typed_builder_derive = { path = "const_typed_builder_derive", version = "=0.2.0" } [dev-dependencies] trybuild = "1.0.84" diff --git a/README.md b/README.md index d7a753f..994d4b8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # `Builder` Derive Macro Documentation - The `Builder` derive macro is used to generate builder methods for structs in Rust. These builder methods allow you to construct instances of the struct by chaining method calls, providing a convenient and readable way to create complex objects with various configurations and compile-time validity checking. This documentation will provide an overview of how to use the `Builder` derive macro. + The `Builder` derive macro is used to generate builder methods for structs in Rust, its biggest feature in this crate is that it provides compile-time validation on the struct. The user can employ several configurations that define the validity of of a complex struct, and it is checked before the struct is ever created. ## Prerequisites @@ -8,7 +8,7 @@ To use the `Builder` derive macro, you should have the `const_typed_builder` cra ```toml [dependencies] -const_typed_builder = "0.1" +const_typed_builder = "0.2" ``` Also, make sure you have the following import statements in your code: @@ -16,7 +16,9 @@ Also, make sure you have the following import statements in your code: ```rust use const_typed_builder::Builder; ``` -## Effects +## Overview + +### Simple example This derive: ```rust use const_typed_builder::Builder; @@ -83,12 +85,15 @@ impl Default for FooData { } } ``` -## Inspirations -Builder macros have been done before, but not exactly what I needed for my use case. Also look into [derive_builder](https://crates.io/crates/derive_builder) and [typed-builder](https://crates.io/crates/typed-builder). Those projects are currently way more mature, but anyone willing to test this crate is currently a godsend. > [!NOTE] -> The current default implementation for checking the validity of grouped fields is `brute_force`. The problem is directly related to [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem), and the `brute_force` implementation currently has a $`O(2^g)`$ where $`g`$ is the amount of grouped variables. This is not a problem with a couple of fields, but it might impact compile time significantly with more fields. Future editions might improve on this. +> Checking the validity of each field is a problem directly related to [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem), which is an NP-complete problem. This has effect especially the grouped fields. The current default implementation for checking the validity of grouped fields is `brute_force`, and this implementation currently has a complexity of $`O(2^g)`$ where $`g`$ is the amount of grouped variables. This is not a problem with a couple of fields, but it might impact compile time significantly with more fields. This can be still optimized significantly. Future editions might improve on this complexity. > -> Although I haven't been able to recreate the issue yet, it seems that const values [aren't guaranteed to be evaluated at compile time](https://doc.rust-lang.org/reference/const_eval.html). This creates the issue that the group verification is not guaranteed to fail in its current implementation when the solver type is set to `compiler`. Users can opt in to the `compiler` solver, which might solve it quicker than `brute_force`, although this is not guaranteed. +> Another implementation is `compiler`. I haven't tested its speed increase yet, but it might have an issue. Although I haven't been able to recreate the issue yet, it seems that const values [aren't guaranteed to be evaluated at compile time](https://doc.rust-lang.org/reference/const_eval.html). This creates the issue that the group verification is not guaranteed to fail during compile-time. +> +> Users can opt in to the `compiler` solver, by adding `#[builder(solver = compiler)]` above the struct. I'm not making any guarantees on its performance. > -> Anyone who would like to help, and add a SAT solver as a dependency (behind a feature flag) is welcome to do so. +> Anyone who would like to help, and add a SAT solver as a dependency (behind a feature flag) is welcome to do so! + +## Inspirations +Builder macros have been done before, but not exactly what I needed for my use case. Also look into [derive_builder](https://crates.io/crates/derive_builder) and [typed-builder](https://crates.io/crates/typed-builder). Those projects are currently way more mature, but anyone willing to test this crate is currently a godsend. diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 2c72f86..b23d86a 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -31,6 +31,7 @@ impl<'a> BuilderGenerator<'a> { /// - `target_vis`: A reference to the visibility of the target struct. /// - `builder_name`: A reference to the identifier representing the builder struct's name. /// - `data_name`: A reference to the identifier representing the data struct's name. + /// - `solve_type`: The type of solver employed for validating the grouped fields /// /// # Returns /// diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index da9ab6b..f71163c 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -152,9 +152,10 @@ impl<'a> FieldGenerator<'a> { quote!(#field_name) }; - match field.kind() { - FieldKind::Optional => quote!(#field_value), - _ => quote!(Some(#field_value)), + if field.kind() == &FieldKind::Optional { + quote!(#field_value) + } else { + quote!(Some(#field_value)) } } } diff --git a/const_typed_builder_derive/src/generator/generics_generator.rs b/const_typed_builder_derive/src/generator/generics_generator.rs index f5cc017..751a81b 100644 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ b/const_typed_builder_derive/src/generator/generics_generator.rs @@ -164,6 +164,11 @@ impl<'a> GenericsGenerator<'a> { self.add_const_generics_for_impl(&mut all) } + /// Adds const generic identifiers to the target structs `syn::Generics` and returns a `syn::Generics` instance. + /// + /// # Returns + /// + /// A `syn::Generics` instance representing the generics for the builder struct. fn add_const_generics_for_impl( &self, tokens: &mut dyn Iterator, @@ -175,7 +180,11 @@ impl<'a> GenericsGenerator<'a> { }); res } - + /// Adds valued const generics to the target structs `syn::Generics` and returns a `Tokenstream` instance. + /// + /// # Returns + /// + /// A `Tokenstream` instance representing the generics for the builder struct. fn add_const_generics_valued_for_type( &self, constants: &mut dyn Iterator>, diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index db9be98..c72f09f 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -28,6 +28,11 @@ impl<'a> GroupGenerator<'a> { Self { groups } } + /// Returns all valid combinations of the const generics for the grouped fields + /// + /// # Returns + /// + /// An `Iterator>` that holds each vector of field indices pub fn valid_groupident_combinations(&self) -> impl Iterator> + '_ { let group_indices: BTreeSet = self .groups @@ -124,6 +129,7 @@ impl<'a> GroupGenerator<'a> { #at_most ) } + /// Generates the correctness check for groups and returns a `TokenStream` as an optional value. /// /// # Returns diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index 20511cf..df7a341 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -21,14 +21,29 @@ pub struct FieldInfo<'a> { kind: FieldKind, } +/// Represents the kind of a field, which can be Optional, Mandatory, or Grouped. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum FieldKind { + /// Indicates an optional field. Optional, + /// Indicates a mandatory field. Mandatory, + /// Indicates a field that is part of one or several groups. Grouped, } impl<'a> FieldInfo<'a> { + /// Creates a new `FieldInfo` instance for a struct field. + /// + /// # Arguments + /// + /// - `field`: A reference to the `syn::Field` representing the field. + /// - `struct_settings`: A mutable reference to `StructSettings` for the struct containing this field. + /// - `index`: The index of the field within the struct. + /// + /// # Returns + /// + /// A `Result` containing the `FieldInfo` instance or an error if the field is unnamed. pub fn new( field: &'a syn::Field, struct_settings: &mut StructSettings, @@ -102,6 +117,7 @@ impl<'a> FieldInfo<'a> { self.propagate } + /// Checks if the field's type is an Option. pub fn is_option_type(&self) -> bool { is_option(&self.field.ty) } @@ -111,18 +127,22 @@ impl<'a> FieldInfo<'a> { &self.field.ty } + /// Retrieves the inner type of the field if it is wrapped in an Option pub fn inner_type(&self) -> Option<&syn::Type> { inner_type(&self.field.ty) } + /// Retrieves the kind of the field, which can be Optional, Mandatory, or Grouped. pub fn kind(&self) -> &FieldKind { &self.kind } + /// Retrieves the index of the field within the struct. pub fn index(&self) -> usize { self.index } + /// Generates a constant identifier based on the field's index. pub fn const_ident(&self) -> syn::Ident { format_ident!("{}{}", CONST_IDENT_PREFIX, self.index) } @@ -163,6 +183,7 @@ pub struct FieldSettings { pub propagate: bool, /// The input name for the builder's setter method. pub input_name: syn::Ident, + /// The groups this field belongs to. pub groups: HashSet, } @@ -192,7 +213,7 @@ impl FieldSettings { /// # Returns /// /// A `syn::Result` indicating success or failure of attribute handling. - pub fn with_attrs(mut self, attrs: &[syn::Attribute]) -> syn::Result { + fn with_attrs(mut self, attrs: &[syn::Attribute]) -> syn::Result { attrs .iter() .map(|attr| self.handle_attribute(attr)) @@ -209,13 +230,41 @@ impl FieldSettings { /// # Returns /// /// The updated `FieldSettings` instance. - pub fn with_ty(mut self, ty: &syn::Type) -> Self { + fn with_ty(mut self, ty: &syn::Type) -> Self { if !self.mandatory && !is_option(ty) { self.mandatory = true; } self } + /// Handles the parsing and processing of a builder attribute applied to a field. + /// + /// This method is responsible for interpreting the meaning of a builder attribute and updating the + /// `FieldSettings` accordingly. It supports the following builder attributes: + /// + /// - `#[builder(mandatory)]`: Marks the field as mandatory, meaning it must be set during the builder + /// construction. If provided without an equals sign (e.g., `#[builder(mandatory)]`), it sets the field as mandatory. + /// If provided with an equals sign (e.g., `#[builder(mandatory = true)]`), it sets the mandatory flag based on the value. + /// + /// - `#[builder(optional)]`: Marks the field as optional, meaning it does not have to be set during + /// the builder construction. If provided without an equals sign (e.g., `#[builder(optional)]`), it sets the field as optional. + /// If provided with an equals sign (e.g., `#[builder(optional = true)]`), it sets the optional flag based on the value. + /// + /// - `#[builder(group = group_name)]`: Associates the field with a group named `group_name`. Fields in the same group + /// are treated as a unit, and at least one of them must be set during builder construction. If the field is marked as mandatory, + /// it cannot be part of a group. This attribute allows specifying the group name both as an identifier (e.g., `group = my_group`) + /// and as a string (e.g., `group = "my_group"`). + /// + /// - `#[builder(propagate)]`: Indicates that the field should propagate its value when the builder is constructed. If this attribute + /// is present, the field's value will be copied or moved to the constructed object when the builder is used to build the object. + /// + /// # Arguments + /// + /// - `attr`: A reference to the `syn::Attribute` representing the builder attribute applied to the field. + /// + /// # Returns + /// + /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) -> syn::Result<()> { if let Some(ident) = attr.path().get_ident() { if ident != BUILDER { diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group_info.rs index dc1a8c9..704ff4d 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group_info.rs @@ -37,9 +37,9 @@ impl GroupInfo { /// Retrieves the expected member count based on the group type. pub fn expected_count(&self) -> usize { match self.group_type { - GroupType::Exact(expected) => expected, - GroupType::AtLeast(expected) => expected, - GroupType::AtMost(expected) => expected, + GroupType::Exact(expected) + | GroupType::AtLeast(expected) + | GroupType::AtMost(expected) => expected, } } @@ -68,7 +68,7 @@ impl GroupInfo { pub fn is_valid_with(&self, indices: &[usize]) -> bool { let applicable_indices_count = self .associated_indices - .intersection(&BTreeSet::from_iter(indices.iter().copied())) + .intersection(&indices.iter().copied().collect()) .count(); match self.group_type { GroupType::Exact(count) => applicable_indices_count == count, @@ -92,7 +92,7 @@ impl Hash for GroupInfo { } } -/// Represents the type of a group, which can be one of three variants: Exact, AtLeast, or AtMost. +/// Represents the type of a group, which can be one of three variants: `Exact`, `AtLeast`, or `AtMost`. #[derive(Debug, Clone)] pub enum GroupType { /// Represents a group with an exact member count. diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index b6b521c..181a97c 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -166,7 +166,7 @@ impl Default for StructSettings { impl StructSettings { /// Creates a new `StructSettings` instance with default values. fn new() -> Self { - Default::default() + StructSettings::default() } pub fn add_mandatory_index(&mut self, index: usize) -> bool { @@ -199,6 +199,17 @@ impl StructSettings { Ok(self) } + /// Handles the parsing and processing of attributes applied to a struct. + /// + /// See the specific functions [`handle_builder_attribute`] and [`handle_group_attribute`] for more information. + /// + /// /// # Arguments + /// + /// - `attr`: A reference to the `syn::Attribute` representing the builder attribute applied to the struct. + /// + /// # Returns + /// + /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) -> syn::Result<()> { if let Some(ident) = attr.path().get_ident() { if ident == GROUP { @@ -213,6 +224,26 @@ impl StructSettings { } } + /// Handles the parsing and processing of builder attributes applied to a struct. + /// + /// This method is responsible for interpreting the meaning of builder attributes applied to the struct and + /// updating the `StructSettings` accordingly. It supports the following builder attributes: + /// + /// - `#[builder(assume_mandatory)]`: Indicates that all fields in the struct should be assumed as mandatory. + /// If provided without an equals sign (e.g., `#[builder(assume_mandatory)]`), it sets the `mandatory` flag for fields to true. + /// If provided with an equals sign (e.g., `#[builder(assume_mandatory = true)]`), it sets the `mandatory` flag for fields based on the value. + /// + /// - `#[builder(solver = `solve_type`)]`: Specifies the solver type to be used for building the struct. The `solve_type` should be one of + /// the predefined solver types, such as `brute_force` or `compiler`. If provided with an equals sign (e.g., `#[builder(solver = brute_force)]`), + /// it sets the `solver_type` accordingly. + /// + /// # Arguments + /// + /// - `attr`: A reference to the `syn::Attribute` representing the builder attribute applied to the struct. + /// + /// # Returns + /// + /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_builder_attribute(&mut self, attr: &syn::Attribute) -> syn::Result<()> { let list = attr.meta.require_list()?; if list.tokens.is_empty() { @@ -247,16 +278,36 @@ impl StructSettings { _ => Err(syn::Error::new_spanned(&path, "Unknown solver type"))?, } } else { - Err(syn::Error::new_spanned(meta.path, "Can't parse solver"))? + Err(syn::Error::new_spanned(meta.path, "Can't parse solver"))?; } } else { - Err(syn::Error::new_spanned(meta.path, "Can't parse solver"))? + Err(syn::Error::new_spanned(meta.path, "Can't parse solver"))?; } } Ok(()) }) } + /// Handles the parsing and processing of group attributes applied to a struct. + /// + /// This method is responsible for interpreting the meaning of group attributes applied to the struct and + /// updating the `StructSettings` accordingly. It supports the following group attributes: + /// + /// - `#[group(group_name = (exact(N)|at_least(N)|at_most(N)|single)]`: + /// Associates fields of the struct with a group named "group_name" and specifies the group's behavior. + /// The `group_name` should be a string identifier. The group can have one of the following behaviors: + /// - `exact(N)`: Exactly N fields in the group must be set during the builder construction. + /// - `at_least(N)`: At least N fields in the group must be set during the builder construction. + /// - `at_most(N)`: At most N fields in the group can be set during the builder construction. + /// - `single`: Only one field in the group can be set during the builder construction. + /// + /// # Arguments + /// + /// - `attr`: A reference to the `syn::Attribute` representing the group attribute applied to the struct. + /// + /// # Returns + /// + /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_group_attribute(&mut self, attr: &syn::Attribute) -> syn::Result<()> { let list = attr.meta.require_list()?; if list.tokens.is_empty() { diff --git a/const_typed_builder_derive/src/symbol.rs b/const_typed_builder_derive/src/symbol.rs index 2ea5aa2..0e4c5c2 100644 --- a/const_typed_builder_derive/src/symbol.rs +++ b/const_typed_builder_derive/src/symbol.rs @@ -6,30 +6,31 @@ use syn::{Ident, Path}; #[derive(Copy, Clone, PartialEq, Eq)] pub struct Symbol<'a>(&'a str); -/// Constant representing the "mandatory" symbol. +/// Constant representing the `mandatory` symbol. pub const MANDATORY: Symbol = Symbol("mandatory"); -/// Constant representing the "group" symbol. +/// Constant representing the `group` symbol. pub const GROUP: Symbol = Symbol("group"); -/// Constant representing the "builder" symbol. +/// Constant representing the `builder` symbol. pub const BUILDER: Symbol = Symbol("builder"); -/// Constant representing the "single" symbol. +/// Constant representing the `single` symbol. pub const SINGLE: Symbol = Symbol("single"); -/// Constant representing the "at_least" symbol. +/// Constant representing the `at_least` symbol. pub const AT_LEAST: Symbol = Symbol("at_least"); -/// Constant representing the "at_most" symbol. +/// Constant representing the `at_most` symbol. pub const AT_MOST: Symbol = Symbol("at_most"); -/// Constant representing the "exact" symbol. +/// Constant representing the `exact` symbol. pub const EXACT: Symbol = Symbol("exact"); -/// Constant representing the "propagate" symbol. +/// Constant representing the `propagate` symbol. pub const PROPAGATE: Symbol = Symbol("propagate"); -/// Constant representing the "assume_mandatory" symbol. +/// Constant representing the `assume_mandatory` symbol. pub const ASSUME_MANDATORY: Symbol = Symbol("assume_mandatory"); -/// Constant representing the "optional" symbol. +/// Constant representing the `optional` symbol. pub const OPTIONAL: Symbol = Symbol("optional"); -/// Constant representing the "solver" symbol. +/// Constant representing the `solver` symbol. pub const SOLVER: Symbol = Symbol("solver"); - +/// Constant representing the `brute_force` symbol. pub const BRUTE_FORCE: Symbol = Symbol("brute_force"); +/// Constant representing the `compiler` symbol. pub const COMPILER: Symbol = Symbol("compiler"); impl<'a> From<&'a String> for Symbol<'a> { diff --git a/const_typed_builder_test/Cargo.toml b/const_typed_builder_test/Cargo.toml index 04b1b44..6145395 100644 --- a/const_typed_builder_test/Cargo.toml +++ b/const_typed_builder_test/Cargo.toml @@ -14,5 +14,5 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dev-dependencies] -const_typed_builder = { path = "../../const_typed_builder", version = "=0.1.1" } +const_typed_builder = { path = "../../const_typed_builder", version = "=0.2.0" } trybuild = "1.0.84" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1b354e3..1d699cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +// #![allow(rustdoc::broken_intra_doc_links)] #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] /// The `Builder` derive macro provides a convenient and ergonomic way to create builder patterns for struct types in Rust. /// It allows you to generate builder methods for constructing instances of a struct with various features and constraints, all checked at compile time. @@ -106,7 +107,7 @@ /// /// ### Examples: /// -/// Valid construction only one field in 'my_group' is provided +/// Valid construction only one field in `my_group` is provided /// ```rust /// # use const_typed_builder::Builder; /// #[derive(Debug, Builder)] @@ -122,7 +123,7 @@ /// .baz("World".to_string()) /// .build(); /// ``` -/// Invalid construction because both fields in 'my_group' are provided +/// Invalid construction because both fields in `my_group` are provided /// ```compile_fail /// # use const_typed_builder::Builder; /// #[derive(Debug, Builder)] @@ -139,7 +140,7 @@ /// .baz("World".to_string()) /// .build(); /// ``` -/// Valid construction because at least 2 fields in 'my_group' are provided: +/// Valid construction because at least 2 fields in `my_group` are provided: /// ```rust /// # use const_typed_builder::Builder; /// #[derive(Debug, Builder)] From 43126f2f9d8bbc0a02101882b2823eed67a85ec0 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 09:29:06 +0200 Subject: [PATCH 09/65] Fix merging issues (#34) No diff From 6f5b68ac78f82eba1ea4115f2f0a09c6036f4333 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:18:15 +0200 Subject: [PATCH 10/65] Fix `use` statements --- .../src/generator/group_generator.rs | 3 +-- const_typed_builder_derive/src/info/group_info.rs | 3 +-- const_typed_builder_derive/src/info/struct_info.rs | 8 +++----- const_typed_builder_derive/src/lib.rs | 1 - 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index c72f09f..a89d914 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeSet; - use crate::{ info::{GroupInfo, GroupType}, CONST_IDENT_PREFIX, @@ -7,6 +5,7 @@ use crate::{ use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use std::collections::BTreeSet; /// The `GroupGenerator` struct is responsible for generating code related to groups within the builder, including correctness checks and verifications. #[derive(Debug)] diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group_info.rs index 704ff4d..b3e55cf 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group_info.rs @@ -1,6 +1,5 @@ -use std::{collections::BTreeSet, hash::Hash}; - use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; +use std::{collections::BTreeSet, hash::Hash}; /// Represents information about a group, including its name, member count, and group type. #[derive(Debug, Clone)] diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 181a97c..9ea313d 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -1,14 +1,12 @@ -use std::collections::{BTreeSet, HashMap}; - use super::field_info::{FieldInfo, FieldSettings}; use super::group_info::{GroupInfo, GroupType}; -use quote::format_ident; -use syn::Token; - use crate::symbol::{ ASSUME_MANDATORY, AT_LEAST, AT_MOST, BRUTE_FORCE, BUILDER, COMPILER, EXACT, GROUP, SINGLE, SOLVER, }; +use quote::format_ident; +use std::collections::{BTreeSet, HashMap}; +use syn::Token; /// A type alias for a collection of `FieldInfo` instances. type FieldInfos<'a> = Vec>; diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index b3ddd58..a2569ff 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -1,7 +1,6 @@ mod generator; mod info; mod symbol; -mod util; use generator::Generator; use info::StructInfo; From 6feb8463213967cb3e502b4fa996c3a8b96e5d43 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:18:22 +0200 Subject: [PATCH 11/65] Move util into field_info.rs --- .../src/info/field_info.rs | 50 ++++++++++++++++--- const_typed_builder_derive/src/util.rs | 35 ------------- 2 files changed, 43 insertions(+), 42 deletions(-) delete mode 100644 const_typed_builder_derive/src/util.rs diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index df7a341..55ca899 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -1,15 +1,13 @@ -use std::collections::HashSet; - +use self::util::{inner_type, is_option}; use super::struct_info::StructSettings; -use proc_macro2::Span; -use quote::format_ident; -use syn::{ExprPath, Token}; - use crate::{ symbol::{BUILDER, GROUP, MANDATORY, OPTIONAL, PROPAGATE}, - util::{inner_type, is_option}, CONST_IDENT_PREFIX, }; +use proc_macro2::Span; +use quote::format_ident; +use std::collections::HashSet; +use syn::{ExprPath, Token}; /// Represents the information about a struct field used for code generation. #[derive(Debug, PartialEq, Eq)] @@ -350,3 +348,41 @@ impl FieldSettings { }) } } + +mod util { + pub fn is_option(ty: &syn::Type) -> bool { + if let syn::Type::Path(type_path) = ty { + if type_path.qself.is_some() { + return false; + } + if let Some(segment) = type_path.path.segments.last() { + segment.ident == syn::Ident::new("Option", segment.ident.span()) + } else { + false + } + } else { + false + } + } + + pub fn inner_type(ty: &syn::Type) -> Option<&syn::Type> { + let path = if let syn::Type::Path(type_path) = ty { + if type_path.qself.is_some() { + return None; + } + &type_path.path + } else { + return None; + }; + let segment = path.segments.last()?; + let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments else { + return None; + }; + + if let syn::GenericArgument::Type(inner) = generic_params.args.first()? { + Some(inner) + } else { + None + } + } +} diff --git a/const_typed_builder_derive/src/util.rs b/const_typed_builder_derive/src/util.rs deleted file mode 100644 index 33aeae2..0000000 --- a/const_typed_builder_derive/src/util.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub fn is_option(ty: &syn::Type) -> bool { - if let syn::Type::Path(type_path) = ty { - if type_path.qself.is_some() { - return false; - } - if let Some(segment) = type_path.path.segments.last() { - segment.ident == syn::Ident::new("Option", segment.ident.span()) - } else { - false - } - } else { - false - } -} - -pub fn inner_type(ty: &syn::Type) -> Option<&syn::Type> { - let path = if let syn::Type::Path(type_path) = ty { - if type_path.qself.is_some() { - return None; - } - &type_path.path - } else { - return None; - }; - let segment = path.segments.last()?; - let syn::PathArguments::AngleBracketed(generic_params) = &segment.arguments else { - return None; - }; - - if let syn::GenericArgument::Type(inner) = generic_params.args.first()? { - Some(inner) - } else { - None - } -} From 8e1c530237b95373742cde1d3a2c04fe5d02f796 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:32:10 +0200 Subject: [PATCH 12/65] Update README.md Add use of crate and usage --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 994d4b8..248fe7e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Also, make sure you have the following import statements in your code: ```rust use const_typed_builder::Builder; ``` -## Overview +## Overview +The crate can be used to check validity of structs at compile time. The user can't call build until the struct is valid. This is done by checking if all mandatory fields are instantiated and all user defined "groups" are valid. ### Simple example This derive: @@ -86,6 +87,36 @@ impl Default for FooData { } ``` +### Attributes +**Struct** +- `#[builder(assume_mandatory)]`: Indicates that all fields in the struct should be assumed as mandatory. + If provided without an equals sign (e.g., `#[builder(assume_mandatory)]`), it sets the `mandatory` flag for fields to true. + If provided with an equals sign (e.g., `#[builder(assume_mandatory = true)]`), it sets the `mandatory` flag for fields based on the value. +- `#[builder(solver = `solve_type`)]`: Specifies the solver type to be used for building the struct. The `solve_type` should be one of + the predefined solver types, such as `brute_force` or `compiler`. If provided with an equals sign (e.g., `#[builder(solver = brute_force)]`), + it sets the `solver_type` accordingly. +- `#[group(group_name = (exact(N)|at_least(N)|at_most(N)|single)]`: + Associates fields of the struct with a group named "group_name" and specifies the group's behavior. + The `group_name` should be a string identifier. The group can have one of the following behaviors: + - `exact(N)`: Exactly N fields in the group must be set during the builder construction. + - `at_least(N)`: At least N fields in the group must be set during the builder construction. + - `at_most(N)`: At most N fields in the group can be set during the builder construction. + - `single`: Only one field in the group can be set during the builder construction. + +**Field** +- `#[builder(mandatory)]`: Marks the field as mandatory, meaning it must be set during the builder + construction. If provided without an equals sign (e.g., `#[builder(mandatory)]`), it sets the field as mandatory. + If provided with an equals sign (e.g., `#[builder(mandatory = true)]`), it sets the mandatory flag based on the value. +- `#[builder(optional)]`: Marks the field as optional, meaning it does not have to be set during + the builder construction. If provided without an equals sign (e.g., `#[builder(optional)]`), it sets the field as optional. + If provided with an equals sign (e.g., `#[builder(optional = true)]`), it sets the optional flag based on the value. +- `#[builder(group = group_name)]`: Associates the field with a group named `group_name`. Fields in the same group + are treated as a unit, and at least one of them must be set during builder construction. If the field is marked as mandatory, + it cannot be part of a group. This attribute allows specifying the group name both as an identifier (e.g., `group = my_group`) + and as a string (e.g., `group = "my_group"`). +- `#[builder(propagate)]`: Indicates that the field should propagate its value when the builder is constructed. If this attribute + is present, the field's value will be copied or moved to the constructed object when the builder is used to build the object. + > [!NOTE] > Checking the validity of each field is a problem directly related to [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem), which is an NP-complete problem. This has effect especially the grouped fields. The current default implementation for checking the validity of grouped fields is `brute_force`, and this implementation currently has a complexity of $`O(2^g)`$ where $`g`$ is the amount of grouped variables. This is not a problem with a couple of fields, but it might impact compile time significantly with more fields. This can be still optimized significantly. Future editions might improve on this complexity. > From 4b028b2e895b560f8e52adc4bf76b91e80eb3589 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:34:45 +0200 Subject: [PATCH 13/65] Update README.md Explain that `#[builder(optional)]` is the exact opposite of `#[builder(mandatory)]` --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 248fe7e..ed2a67f 100644 --- a/README.md +++ b/README.md @@ -107,8 +107,8 @@ impl Default for FooData { - `#[builder(mandatory)]`: Marks the field as mandatory, meaning it must be set during the builder construction. If provided without an equals sign (e.g., `#[builder(mandatory)]`), it sets the field as mandatory. If provided with an equals sign (e.g., `#[builder(mandatory = true)]`), it sets the mandatory flag based on the value. -- `#[builder(optional)]`: Marks the field as optional, meaning it does not have to be set during - the builder construction. If provided without an equals sign (e.g., `#[builder(optional)]`), it sets the field as optional. +- `#[builder(optional)]`: Marks the field as optional, this is the exact opposite of mandatory. + If provided without an equals sign (e.g., `#[builder(optional)]`), it sets the field as optional. If provided with an equals sign (e.g., `#[builder(optional = true)]`), it sets the optional flag based on the value. - `#[builder(group = group_name)]`: Associates the field with a group named `group_name`. Fields in the same group are treated as a unit, and at least one of them must be set during builder construction. If the field is marked as mandatory, From bb25e7ea373799f29ffdf0636b126e9407ba4be2 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:41:47 +0200 Subject: [PATCH 14/65] Update README.md Fix small formatting issue --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ed2a67f..c402ac5 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,9 @@ impl Default for FooData { - `#[builder(assume_mandatory)]`: Indicates that all fields in the struct should be assumed as mandatory. If provided without an equals sign (e.g., `#[builder(assume_mandatory)]`), it sets the `mandatory` flag for fields to true. If provided with an equals sign (e.g., `#[builder(assume_mandatory = true)]`), it sets the `mandatory` flag for fields based on the value. -- `#[builder(solver = `solve_type`)]`: Specifies the solver type to be used for building the struct. The `solve_type` should be one of +- `#[builder(solver = )]`: Specifies the solver type to be used for building the struct. The `solve_type` should be one of the predefined solver types, such as `brute_force` or `compiler`. If provided with an equals sign (e.g., `#[builder(solver = brute_force)]`), - it sets the `solver_type` accordingly. + it sets the "solver type" accordingly. - `#[group(group_name = (exact(N)|at_least(N)|at_most(N)|single)]`: Associates fields of the struct with a group named "group_name" and specifies the group's behavior. The `group_name` should be a string identifier. The group can have one of the following behaviors: @@ -107,7 +107,7 @@ impl Default for FooData { - `#[builder(mandatory)]`: Marks the field as mandatory, meaning it must be set during the builder construction. If provided without an equals sign (e.g., `#[builder(mandatory)]`), it sets the field as mandatory. If provided with an equals sign (e.g., `#[builder(mandatory = true)]`), it sets the mandatory flag based on the value. -- `#[builder(optional)]`: Marks the field as optional, this is the exact opposite of mandatory. +- `#[builder(optional)]`: Marks the field as optional, this is the exact opposite of `#[builder(mandatory)]`. If provided without an equals sign (e.g., `#[builder(optional)]`), it sets the field as optional. If provided with an equals sign (e.g., `#[builder(optional = true)]`), it sets the optional flag based on the value. - `#[builder(group = group_name)]`: Associates the field with a group named `group_name`. Fields in the same group From 3644cfe1ff41fa629a72ea1186d79b1b3eda8ab1 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:39:15 +0200 Subject: [PATCH 15/65] Update README.md Fix minor mistake in expansion --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c402ac5..e250066 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ impl Builder for Foo { } } #[derive(Debug)] -pub struct FooBuilder { +pub struct FooBuilder { data: FooData, } impl FooBuilder { From f7351e134fc201e034d1e7f5fa47f315fd3898db Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 13:22:46 +0200 Subject: [PATCH 16/65] Change data field in MyTypeBuilder to __my_type_data --- const_typed_builder_derive/Cargo.toml | 1 + .../src/generator/builder_generator.rs | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/const_typed_builder_derive/Cargo.toml b/const_typed_builder_derive/Cargo.toml index 005828d..6803035 100644 --- a/const_typed_builder_derive/Cargo.toml +++ b/const_typed_builder_derive/Cargo.toml @@ -18,6 +18,7 @@ quote = "1.0" proc-macro2 = "1.0" either = "1.9" itertools = "0.11.0" +convert_case = "0.6" [lib] proc-macro = true diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index b23d86a..39dc0f5 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -3,8 +3,9 @@ use super::{ group_generator::GroupGenerator, }; use crate::{info::SolveType, StreamResult, VecStreamResult}; +use convert_case::{Case, Casing}; use proc_macro2::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; // The `BuilderGenerator` struct is responsible for generating code related to the builder struct, /// including its definition, implementation of setter methods, `new` method, and `build` method. @@ -59,6 +60,10 @@ impl<'a> BuilderGenerator<'a> { } } + fn data_field_ident(&self) -> syn::Ident { + format_ident!("__{}", self.data_name.to_string().to_case(Case::Snake)) + } + // Generates the code for the builder struct and its methods and returns a token stream. /// /// # Returns @@ -77,6 +82,7 @@ impl<'a> BuilderGenerator<'a> { /// Generates the code for the builder struct definition. fn generate_struct(&self) -> TokenStream { let data_name = self.data_name; + let data_field = self.data_field_ident(); let builder_name = self.builder_name; let generics = self.generics_gen.builder_struct_generics(); @@ -89,7 +95,7 @@ impl<'a> BuilderGenerator<'a> { quote!( #[derive(Debug)] #vis struct #builder_name #impl_generics #where_clause { - data: #data_name #type_generics + #data_field: #data_name #type_generics } ) } @@ -112,6 +118,7 @@ impl<'a> BuilderGenerator<'a> { fn generate_new_impl(&self) -> TokenStream { let builder_name = self.builder_name; let data_name = self.data_name; + let data_field = self.data_field_ident(); let type_generics = self.generics_gen.const_generics_valued(false); let (impl_generics, _, where_clause) = self.generics_gen.target_generics().split_for_impl(); @@ -126,7 +133,7 @@ impl<'a> BuilderGenerator<'a> { impl #impl_generics Default for #builder_name #type_generics #where_clause { fn default() -> Self { #builder_name { - data: #data_name::default(), + #data_field: #data_name::default(), } } } @@ -137,6 +144,7 @@ impl<'a> BuilderGenerator<'a> { fn generate_build_impl(&self) -> TokenStream { let builder_name = self.builder_name; let target_name = self.target_name; + let data_field = self.data_field_ident(); let (impl_generics, target_type_generics, where_clause) = self.generics_gen.target_generics().split_for_impl(); @@ -154,7 +162,7 @@ impl<'a> BuilderGenerator<'a> { impl #impl_generics #builder_name #type_generics #where_clause{ pub fn build(self) -> #target_name #target_type_generics { - self.data.into() + self.#data_field.into() } } ) @@ -189,7 +197,7 @@ impl<'a> BuilderGenerator<'a> { pub fn build(self) -> #target_name #target_type_generics { #correctness_check - self.data.into() + self.#data_field.into() } } ) @@ -200,6 +208,7 @@ impl<'a> BuilderGenerator<'a> { /// Generates the code for the setter methods of the builder. fn generate_setters_impl(&self) -> StreamResult { let builder_name = self.builder_name; + let data_field = self.data_field_ident(); let setters = self .field_gen .fields().iter().map(|field| { @@ -215,10 +224,10 @@ impl<'a> BuilderGenerator<'a> { let tokens = quote!( impl #const_idents_impl #builder_name #const_idents_type_input #where_clause { pub fn #field_name (self, #input_type) -> #builder_name #const_idents_type_output { - let mut data = self.data; - data.#field_name = #input_value; + let mut #data_field = self.#data_field; + #data_field.#field_name = #input_value; #builder_name { - data, + #data_field, } } } From 6292f074afc83246bd617f0e9b0affe1b5e56c6c Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:14:42 +0200 Subject: [PATCH 17/65] Update README.md Add actual examples --- README.md | 116 ++++++++++++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index e250066..2eeb366 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # `Builder` Derive Macro Documentation - The `Builder` derive macro is used to generate builder methods for structs in Rust, its biggest feature in this crate is that it provides compile-time validation on the struct. The user can employ several configurations that define the validity of of a complex struct, and it is checked before the struct is ever created. +The `Builder` derive macro is used to generate builder methods for structs in Rust, its biggest feature in this crate is that it provides compile-time validation on the struct. The user can employ several configurations that define the validity of of a complex struct, and it is checked before the struct is ever created. - ## Prerequisites +## Prerequisites To use the `Builder` derive macro, you should have the `const_typed_builder` crate added to your project's dependencies in your `Cargo.toml` file: @@ -19,72 +19,67 @@ use const_typed_builder::Builder; ## Overview The crate can be used to check validity of structs at compile time. The user can't call build until the struct is valid. This is done by checking if all mandatory fields are instantiated and all user defined "groups" are valid. -### Simple example -This derive: +### Examples +Basic usage: ```rust use const_typed_builder::Builder; + #[derive(Debug, Builder)] pub struct Foo { bar: String, } + +let foo = Foo::builder() + .bar("Hello world!".to_string()) // The program would not compile without this call + .build() // Because this function is only implemented for the version of FooBuilder where `bar` is initialized ``` -Expands to this code: + +Subset of launchd: ```rust use const_typed_builder::Builder; -#[derive(Debug)] -pub struct Foo { - bar: String, -} -impl Builder for Foo { - type BuilderImpl = FooBuilder; - fn builder() -> Self::BuilderImpl { - Self::BuilderImpl::new() - } -} -#[derive(Debug)] -pub struct FooBuilder { - data: FooData, -} -impl FooBuilder { - pub fn new() -> FooBuilder { - Self::default() - } -} -impl Default for FooBuilder { - fn default() -> Self { - FooBuilder { - data: FooData::default(), - } - } -} -impl FooBuilder { - pub fn bar(self, bar: String) -> FooBuilder { - let mut data = self.data; - data.bar = Some(bar); - FooBuilder { data } - } -} -impl FooBuilder { - pub fn build(self) -> Foo { - self.data.into() - } -} -#[derive(Debug)] -pub struct FooData { - pub bar: Option, -} -impl From for Foo { - fn from(data: FooData) -> Foo { - Foo { - bar: data.bar.unwrap(), - } - } + +#[derive(Debug, Builder)] +pub struct ResourceLimits { + core: Option, + // ... } -impl Default for FooData { - fn default() -> Self { - FooData { bar: None } - } +#[derive(Debug, Builder)] +#[group(program = at_least(1), deprecated = at_most(0))] +// ^ `my_group = at_most(0)` can be used to denote deprecated +// fields that you still want to deserialize. It will be replaced by the attribute +// `#[builder(skip)]` on a field in a future version +pub struct Launchd { + #[builder(mandatory)] + label: Option, + disabled: Option, + user_name: Option, + group_name: Option, + // ... + #[builder(group = program)] + program: Option, + bundle_program: Option, + #[builder(group = program)] + program_arguments: Option>, + // ... + #[builder(group = deprecated)] + on_demand: Option, // NB: deprecated (see KeepAlive), but still needed for reading old plists. + #[builder(group = deprecated)] + service_ipc: Option, // NB: "Please remove this key from your launchd.plist." + // ... + #[builder(propagate)] + soft_resource_limits: Option, + #[builder(propagate)] + hard_resource_limits: Option, + // ... } + +let launchd = Launchd::builder() + .label("my_label".to_string()) // <- 1: Mandatory + .program("./my_program".into()) // <- 2: Launchd expects that least one of these fields is set.. + .program_arguments(vec!["my_arg".to_string()]) // <- 2: .. We can remove either one, but never both +// .on_demand(false) <- 3: If this is uncommented then the struct will never be valid + .soft_resource_limits(|builder| builder.core(Some(1)).build()) // <- 4: Propagating to `ResourceLimits::builder` + .build() ``` ### Attributes @@ -92,9 +87,6 @@ impl Default for FooData { - `#[builder(assume_mandatory)]`: Indicates that all fields in the struct should be assumed as mandatory. If provided without an equals sign (e.g., `#[builder(assume_mandatory)]`), it sets the `mandatory` flag for fields to true. If provided with an equals sign (e.g., `#[builder(assume_mandatory = true)]`), it sets the `mandatory` flag for fields based on the value. -- `#[builder(solver = )]`: Specifies the solver type to be used for building the struct. The `solve_type` should be one of - the predefined solver types, such as `brute_force` or `compiler`. If provided with an equals sign (e.g., `#[builder(solver = brute_force)]`), - it sets the "solver type" accordingly. - `#[group(group_name = (exact(N)|at_least(N)|at_most(N)|single)]`: Associates fields of the struct with a group named "group_name" and specifies the group's behavior. The `group_name` should be a string identifier. The group can have one of the following behaviors: @@ -102,6 +94,10 @@ impl Default for FooData { - `at_least(N)`: At least N fields in the group must be set during the builder construction. - `at_most(N)`: At most N fields in the group can be set during the builder construction. - `single`: Only one field in the group can be set during the builder construction. +- `#[builder(solver = )]`: **Use sparingly, see note at bottom of this file!** Specifies the solver type to be used for building the struct. The `solve_type` + should be one of the predefined solver types, such as `brute_force` or `compiler`. If provided with an equals sign (e.g., `#[builder(solver = brute_force)]`), + it sets the "solver type" accordingly. This attribute is still tested, and `brute_force` is the default, and only if there are problems in compilation time + then you can try `compiler`. `compiler` gives less guarantees though. **Field** - `#[builder(mandatory)]`: Marks the field as mandatory, meaning it must be set during the builder From cc122daba21489b54e5cbb08476062b88b1631e0 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:19:49 +0200 Subject: [PATCH 18/65] Update README.md Add a ; Make lines shorter --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2eeb366..b263c16 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,9 @@ pub struct Foo { } let foo = Foo::builder() - .bar("Hello world!".to_string()) // The program would not compile without this call - .build() // Because this function is only implemented for the version of FooBuilder where `bar` is initialized + .bar("Hello world!".to_string()) // <- The program would not compile without this call + .build(); // <- Because this function is only implemented for the + // .. version of FooBuilder where `bar` is initialized ``` Subset of launchd: @@ -74,12 +75,16 @@ pub struct Launchd { } let launchd = Launchd::builder() - .label("my_label".to_string()) // <- 1: Mandatory - .program("./my_program".into()) // <- 2: Launchd expects that least one of these fields is set.. - .program_arguments(vec!["my_arg".to_string()]) // <- 2: .. We can remove either one, but never both -// .on_demand(false) <- 3: If this is uncommented then the struct will never be valid - .soft_resource_limits(|builder| builder.core(Some(1)).build()) // <- 4: Propagating to `ResourceLimits::builder` - .build() + .label("my_label".to_string()) // <- 1: Mandatory + .program("./my_program".into()) // <- 2: Launchd expects that least one of these fields is set.. + .program_arguments( // <- 2: .. We can remove either one, but never both + vec!["my_arg".to_string()] + ) +// .on_demand(false) <- 3: If this is uncommented then the struct will never be valid + .soft_resource_limits(|builder| + builder.core(Some(1)).build() // <- 4: Propagating to `ResourceLimits::builder` + ) + .build(); ``` ### Attributes From 0efc9233a06670b53080f80ec8a3c39eefab8c15 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:21:46 +0200 Subject: [PATCH 19/65] Update README.md use std::path::PathBuf; --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b263c16..244c43e 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ let foo = Foo::builder() Subset of launchd: ```rust use const_typed_builder::Builder; +use std::path::PathBuf; #[derive(Debug, Builder)] pub struct ResourceLimits { From 0ac99dbc6303584adbef709eb3b8b1581dfa5e59 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:40:37 +0200 Subject: [PATCH 20/65] Update README.md Add Some --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 244c43e..ec8c17b 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ let launchd = Launchd::builder() ) // .on_demand(false) <- 3: If this is uncommented then the struct will never be valid .soft_resource_limits(|builder| - builder.core(Some(1)).build() // <- 4: Propagating to `ResourceLimits::builder` + Some(builder.core(Some(1)).build()) // <- 4: Propagating to `ResourceLimits::builder` ) .build(); ``` From f0d4dcb3d717accdfd9aa72e3f487b4d8d0a4aaa Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:49:43 +0200 Subject: [PATCH 21/65] Removed Debug from builder and data struct --- const_typed_builder_derive/src/generator/builder_generator.rs | 1 - const_typed_builder_derive/src/generator/data_generator.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 39dc0f5..b929922 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -93,7 +93,6 @@ impl<'a> BuilderGenerator<'a> { let vis = self.target_vis; quote!( - #[derive(Debug)] #vis struct #builder_name #impl_generics #where_clause { #data_field: #data_name #type_generics } diff --git a/const_typed_builder_derive/src/generator/data_generator.rs b/const_typed_builder_derive/src/generator/data_generator.rs index 94ed40d..ef0ab22 100644 --- a/const_typed_builder_derive/src/generator/data_generator.rs +++ b/const_typed_builder_derive/src/generator/data_generator.rs @@ -94,7 +94,6 @@ impl<'a> DataGenerator<'a> { self.generics_gen.target_generics().split_for_impl(); let tokens = quote!( - #[derive(Debug)] pub struct #data_name #impl_generics #where_clause{ #(#fields),* } From 72ecdf221e58e53e353434b390b359c364cd4357 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:50:06 +0200 Subject: [PATCH 22/65] Add test --- const_typed_builder_test/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index 9dc6da0..cbc4494 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -655,4 +655,14 @@ mod test { .build(); assert_eq!(expected, foo); } + + #[test] + fn no_other_derive_necessary() { + #[derive(Builder)] + pub struct Foo { + bar: String, + } + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(foo.bar, "Hello world!"); + } } From f3a3afd0f9cc9b2ca7707dd7a0dd2ccb0c3078b0 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:14:11 +0200 Subject: [PATCH 23/65] Add skip Symbol --- const_typed_builder_derive/src/symbol.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/const_typed_builder_derive/src/symbol.rs b/const_typed_builder_derive/src/symbol.rs index 0e4c5c2..1512a42 100644 --- a/const_typed_builder_derive/src/symbol.rs +++ b/const_typed_builder_derive/src/symbol.rs @@ -32,6 +32,8 @@ pub const SOLVER: Symbol = Symbol("solver"); pub const BRUTE_FORCE: Symbol = Symbol("brute_force"); /// Constant representing the `compiler` symbol. pub const COMPILER: Symbol = Symbol("compiler"); +/// Constant representing the `skip` symbol. +pub const SKIP: Symbol = Symbol("skip"); impl<'a> From<&'a String> for Symbol<'a> { fn from(value: &'a String) -> Self { From 502f3d6b9dd397147ba623f26ea0aad61e19da32 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:15:12 +0200 Subject: [PATCH 24/65] Add Skipped kind to FieldKind, parse skip symbol --- .../src/info/field_info.rs | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index 55ca899..af45bc5 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -1,7 +1,6 @@ -use self::util::{inner_type, is_option}; use super::struct_info::StructSettings; use crate::{ - symbol::{BUILDER, GROUP, MANDATORY, OPTIONAL, PROPAGATE}, + symbol::{BUILDER, GROUP, MANDATORY, OPTIONAL, PROPAGATE, SKIP}, CONST_IDENT_PREFIX, }; use proc_macro2::Span; @@ -28,6 +27,8 @@ pub enum FieldKind { Mandatory, /// Indicates a field that is part of one or several groups. Grouped, + /// Indicates a field that not included in the builder. + Skipped, } impl<'a> FieldInfo<'a> { @@ -62,7 +63,15 @@ impl<'a> FieldInfo<'a> { .with_ty(ty) .with_attrs(attrs)?; - let info = if settings.mandatory { + let info = if settings.skipped { + Self { + field, + ident, + index, + propagate: settings.propagate, + kind: FieldKind::Skipped, + } + } else if settings.mandatory { struct_settings.add_mandatory_index(index); // TODO: Check bool Self { field, @@ -117,7 +126,7 @@ impl<'a> FieldInfo<'a> { /// Checks if the field's type is an Option. pub fn is_option_type(&self) -> bool { - is_option(&self.field.ty) + util::is_option(&self.field.ty) } /// Retrieves the type of the field. @@ -127,7 +136,7 @@ impl<'a> FieldInfo<'a> { /// Retrieves the inner type of the field if it is wrapped in an Option pub fn inner_type(&self) -> Option<&syn::Type> { - inner_type(&self.field.ty) + util::inner_type(&self.field.ty) } /// Retrieves the kind of the field, which can be Optional, Mandatory, or Grouped. @@ -146,16 +155,19 @@ impl<'a> FieldInfo<'a> { } /// Retrieves the input type for the builder's setter method. - pub fn setter_input_type(&self) -> &syn::Type { + pub fn setter_input_type(&self) -> Option<&syn::Type> { match self.kind() { - FieldKind::Optional => self.ty(), - FieldKind::Mandatory if self.is_option_type() => self.inner_type().expect( + FieldKind::Optional => Some(self.ty()), + FieldKind::Mandatory if self.is_option_type() => Some(self.inner_type().expect( "Couldn't read inner type of option, even though it's seen as an Option type", - ), - FieldKind::Mandatory => self.ty(), - FieldKind::Grouped => self - .inner_type() - .expect("Couldn't read inner type of option, even though it's marked as grouped"), + )), + FieldKind::Mandatory => Some(self.ty()), + FieldKind::Grouped => { + Some(self.inner_type().expect( + "Couldn't read inner type of option, even though it's marked as grouped", + )) + } + FieldKind::Skipped => None, } } } @@ -175,6 +187,7 @@ impl<'a> Ord for FieldInfo<'a> { /// Represents settings for struct field generation. #[derive(Debug, Clone)] pub struct FieldSettings { + pub skipped: bool, /// Indicates if the field is mandatory. pub mandatory: bool, /// Indicates if the field should propagate values. @@ -188,6 +201,7 @@ pub struct FieldSettings { impl Default for FieldSettings { fn default() -> FieldSettings { FieldSettings { + skipped: false, mandatory: false, propagate: false, input_name: syn::Ident::new("input", Span::call_site()), @@ -229,7 +243,7 @@ impl FieldSettings { /// /// The updated `FieldSettings` instance. fn with_ty(mut self, ty: &syn::Type) -> Self { - if !self.mandatory && !is_option(ty) { + if !self.mandatory && !util::is_option(ty) { self.mandatory = true; } self @@ -275,6 +289,20 @@ impl FieldSettings { } attr.parse_nested_meta(|meta| { + if meta.path == SKIP { + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(syn::LitBool { value, .. }), + .. + }) = expr + { + self.skipped = value; + } + } else { + self.skipped = true; + } + } if meta.path == MANDATORY { if meta.input.peek(Token![=]) { let expr: syn::Expr = meta.value()?.parse()?; From 219b7fa36686fbf5cb00e79c64d676e854acca7c Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:15:40 +0200 Subject: [PATCH 25/65] Get field generator and field generator to skip the skipped field --- .../src/generator/field_generator.rs | 32 ++++++++++++------- .../src/generator/generics_generator.rs | 14 ++++---- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index f71163c..f44a788 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -38,10 +38,11 @@ impl<'a> FieldGenerator<'a> { pub fn data_struct_fields(&self) -> VecStreamResult { self.fields .iter() - .map(|field| { + .filter_map(|field| { let field_name = field.ident(); let data_field_type = match field.kind() { + FieldKind::Skipped => return None, FieldKind::Optional => field.ty().to_token_stream(), FieldKind::Mandatory if field.is_option_type() => field.ty().to_token_stream(), FieldKind::Mandatory => { @@ -54,7 +55,7 @@ impl<'a> FieldGenerator<'a> { let tokens = quote!( pub #field_name: #data_field_type ); - Ok(tokens) + Some(Ok(tokens)) }) .collect() } @@ -67,9 +68,10 @@ impl<'a> FieldGenerator<'a> { pub fn data_impl_from_fields(&self) -> VecStreamResult { self.fields .iter() - .map(|field| { + .filter_map(|field| { let field_name = field.ident(); let tokens = match field.kind() { + FieldKind::Skipped => return None, FieldKind::Mandatory if field.is_option_type() => { quote!(#field_name: data.#field_name) } @@ -80,7 +82,7 @@ impl<'a> FieldGenerator<'a> { quote!(#field_name: data.#field_name.unwrap()) } }; - Ok(tokens) + Some(Ok(tokens)) }) .collect() } @@ -109,13 +111,16 @@ impl<'a> FieldGenerator<'a> { /// # Returns /// /// A `TokenStream` representing the generated input type for the builder setter method. - pub fn builder_set_impl_input_type(&self, field: &'a FieldInfo) -> TokenStream { + pub fn builder_set_impl_input_type(&self, field: &'a FieldInfo) -> Option { + if field.kind() == &FieldKind::Skipped { + return None; + } let field_name = field.ident(); let field_ty = field.setter_input_type(); let bottom_ty = if field.is_option_type() { field.inner_type().unwrap() } else { - field_ty + field_ty.unwrap() }; let field_ty = if field.propagate() { @@ -124,7 +129,7 @@ impl<'a> FieldGenerator<'a> { quote!(#field_ty) }; - quote!(#field_name: #field_ty) + Some(quote!(#field_name: #field_ty)) } /// Generates code for the input value of a builder setter method and returns a token stream. @@ -136,14 +141,17 @@ impl<'a> FieldGenerator<'a> { /// # Returns /// /// A `TokenStream` representing the generated input value for the builder setter method. - pub fn builder_set_impl_input_value(&self, field: &'a FieldInfo) -> TokenStream { - let field_name = field.ident(); + pub fn builder_set_impl_input_value(&self, field: &'a FieldInfo) -> Option { + if field.kind() == &FieldKind::Skipped { + return None; + } + let field_name = field.ident(); let field_ty = field.setter_input_type(); let bottom_ty = if field.is_option_type() { field.inner_type().unwrap() } else { - field_ty + field_ty.unwrap() }; let field_value = if field.propagate() { @@ -153,9 +161,9 @@ impl<'a> FieldGenerator<'a> { }; if field.kind() == &FieldKind::Optional { - quote!(#field_value) + Some(quote!(#field_value)) } else { - quote!(Some(#field_value)) + Some(quote!(Some(#field_value))) } } } diff --git a/const_typed_builder_derive/src/generator/generics_generator.rs b/const_typed_builder_derive/src/generator/generics_generator.rs index 751a81b..ab652d3 100644 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ b/const_typed_builder_derive/src/generator/generics_generator.rs @@ -45,7 +45,7 @@ impl<'a> GenericsGenerator<'a> { /// A `TokenStream` representing the generated const generics. pub fn const_generics_valued(&self, value: bool) -> TokenStream { let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Optional => None, + FieldKind::Skipped | FieldKind::Optional => None, FieldKind::Mandatory | FieldKind::Grouped => Some(Either::Right(syn::LitBool::new( value, field.ident().span(), @@ -70,7 +70,7 @@ impl<'a> GenericsGenerator<'a> { value: bool, ) -> TokenStream { let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Optional => None, + FieldKind::Skipped | FieldKind::Optional => None, _ if field == field_info => Some(Either::Right(syn::LitBool::new( value, field_info.ident().span(), @@ -91,7 +91,7 @@ impl<'a> GenericsGenerator<'a> { /// A `syn::Generics` instance representing the generated const generics for the setter method input type. pub fn builder_const_generic_idents_set_impl(&self, field_info: &FieldInfo) -> syn::Generics { let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Optional => None, + FieldKind::Skipped | FieldKind::Optional => None, _ if field == field_info => None, FieldKind::Mandatory | FieldKind::Grouped => Some(field.const_ident()), }); @@ -105,7 +105,7 @@ impl<'a> GenericsGenerator<'a> { /// A `TokenStream` representing the generated const generics for the builder `build` method. pub fn builder_const_generic_idents_build(&self, true_indices: &[usize]) -> TokenStream { let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Optional => None, + FieldKind::Skipped | FieldKind::Optional => None, FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( true, proc_macro2::Span::call_site(), @@ -128,7 +128,7 @@ impl<'a> GenericsGenerator<'a> { /// A `TokenStream` representing the generated const generics for the builder `build` method. pub fn builder_const_generic_idents_build_unset_group(&self) -> TokenStream { let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Optional => None, + FieldKind::Skipped | FieldKind::Optional => None, FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( true, proc_macro2::Span::call_site(), @@ -145,7 +145,7 @@ impl<'a> GenericsGenerator<'a> { /// A `syn::Generics` instance representing the generated const generics for builder group partial identifiers. pub fn builder_const_generic_group_partial_idents(&self) -> syn::Generics { let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Optional | FieldKind::Mandatory => None, + FieldKind::Skipped | FieldKind::Optional | FieldKind::Mandatory => None, FieldKind::Grouped => Some(field.const_ident()), }); self.add_const_generics_for_impl(&mut all) @@ -158,7 +158,7 @@ impl<'a> GenericsGenerator<'a> { /// A `syn::Generics` instance representing the generated const generics for the builder struct. pub fn builder_struct_generics(&self) -> syn::Generics { let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Optional => None, + FieldKind::Skipped | FieldKind::Optional => None, FieldKind::Mandatory | FieldKind::Grouped => Some(field.const_ident()), }); self.add_const_generics_for_impl(&mut all) From 0e0166ebd4bc78ecb707787b7c6ff677607ce4e0 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:29:25 +0200 Subject: [PATCH 26/65] Add test --- const_typed_builder_test/src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index cbc4494..ff443fe 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -665,4 +665,20 @@ mod test { let foo = Foo::builder().bar("Hello world!".to_string()).build(); assert_eq!(foo.bar, "Hello world!"); } + + #[test] + fn skip_field() { + #[derive(Debug, PartialEq, Builder)] + pub struct Foo { + bar: String, + #[builder(skip)] + baz: Option, + } + let expected = Foo { + bar: "Hello world!".to_string(), + baz: None + }; + let foo = Foo::builder().bar("Hello world!".to_string()).build(); + assert_eq!(foo, expected); + } } From 2fac681cc13e77c5973ff9e556c35329faa6bdf2 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:29:50 +0200 Subject: [PATCH 27/65] Get builder generator to work --- .../src/generator/builder_generator.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index b929922..6766c8a 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -2,7 +2,10 @@ use super::{ field_generator::FieldGenerator, generics_generator::GenericsGenerator, group_generator::GroupGenerator, }; -use crate::{info::SolveType, StreamResult, VecStreamResult}; +use crate::{ + info::{FieldKind, SolveType}, + StreamResult, VecStreamResult, +}; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -210,7 +213,10 @@ impl<'a> BuilderGenerator<'a> { let data_field = self.data_field_ident(); let setters = self .field_gen - .fields().iter().map(|field| { + .fields() + .iter() + .filter(|field| field.kind() != &FieldKind::Skipped) + .map(|field| { let const_idents_impl = self.generics_gen.builder_const_generic_idents_set_impl(field); let const_idents_type_input = self.generics_gen.builder_const_generic_idents_set_type(field, false); let const_idents_type_output = self.generics_gen.builder_const_generic_idents_set_type(field, true); From c267dfae58130d8cee2eb3327c548b7d1affb261 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:29:59 +0200 Subject: [PATCH 28/65] Fix issue in field generator --- .../src/generator/field_generator.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index f44a788..9ca2c8d 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -68,10 +68,10 @@ impl<'a> FieldGenerator<'a> { pub fn data_impl_from_fields(&self) -> VecStreamResult { self.fields .iter() - .filter_map(|field| { + .map(|field| { let field_name = field.ident(); let tokens = match field.kind() { - FieldKind::Skipped => return None, + FieldKind::Skipped => quote!(#field_name: None), FieldKind::Mandatory if field.is_option_type() => { quote!(#field_name: data.#field_name) } @@ -82,7 +82,7 @@ impl<'a> FieldGenerator<'a> { quote!(#field_name: data.#field_name.unwrap()) } }; - Some(Ok(tokens)) + Ok(tokens) }) .collect() } @@ -93,10 +93,14 @@ impl<'a> FieldGenerator<'a> { /// /// A `TokenStream` representing the generated default field values. pub fn data_impl_default_fields(&self) -> TokenStream { - let fields_none = self.fields.iter().map(|field| { - let field_name = field.ident(); - quote!(#field_name: None) - }); + let fields_none = self + .fields + .iter() + .filter(|field| field.kind() != &FieldKind::Skipped) + .map(|field| { + let field_name = field.ident(); + quote!(#field_name: None) + }); quote!( #(#fields_none),* ) From fe5113407c9d73ac04d874a1e56e1afa98cf229e Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:30:07 +0200 Subject: [PATCH 29/65] Add failing test --- const_typed_builder_test/compile_fail/skip_field_1.rs | 10 ++++++++++ .../compile_fail/skip_field_1.stderr | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 const_typed_builder_test/compile_fail/skip_field_1.rs create mode 100644 const_typed_builder_test/compile_fail/skip_field_1.stderr diff --git a/const_typed_builder_test/compile_fail/skip_field_1.rs b/const_typed_builder_test/compile_fail/skip_field_1.rs new file mode 100644 index 0000000..d9e1038 --- /dev/null +++ b/const_typed_builder_test/compile_fail/skip_field_1.rs @@ -0,0 +1,10 @@ +use const_typed_builder::Builder; +fn main() { + #[derive(Debug, PartialEq, Builder)] + pub struct Foo { + bar: String, + #[builder(skip)] + baz: Option, + } + let foo = Foo::builder().bar("Hello world!".to_string()).baz("Skipped".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/skip_field_1.stderr b/const_typed_builder_test/compile_fail/skip_field_1.stderr new file mode 100644 index 0000000..3a7cac9 --- /dev/null +++ b/const_typed_builder_test/compile_fail/skip_field_1.stderr @@ -0,0 +1,8 @@ +error[E0599]: no method named `baz` found for struct `FooBuilder` in the current scope + --> ./compile_fail/skip_field_1.rs:9:62 + | +3 | #[derive(Debug, PartialEq, Builder)] + | ------- method `baz` not found for this struct +... +9 | let foo = Foo::builder().bar("Hello world!".to_string()).baz("Skipped".to_string()).build(); + | ^^^ method not found in `FooBuilder` From c9bd2ad5da1b8091707561092a626eddb836a691 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:32:00 +0200 Subject: [PATCH 30/65] cargo fmt, add comma --- const_typed_builder_test/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index ff443fe..1d0b4b7 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -676,7 +676,7 @@ mod test { } let expected = Foo { bar: "Hello world!".to_string(), - baz: None + baz: None, }; let foo = Foo::builder().bar("Hello world!".to_string()).build(); assert_eq!(foo, expected); From 48bf3c3c98c798b96930321b57bc8b9d40af98a6 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 15:45:45 +0200 Subject: [PATCH 31/65] Add proc-macro-error --- const_typed_builder_derive/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/const_typed_builder_derive/Cargo.toml b/const_typed_builder_derive/Cargo.toml index 6803035..730eeab 100644 --- a/const_typed_builder_derive/Cargo.toml +++ b/const_typed_builder_derive/Cargo.toml @@ -19,6 +19,7 @@ proc-macro2 = "1.0" either = "1.9" itertools = "0.11.0" convert_case = "0.6" +proc-macro-error = "1.0" [lib] proc-macro = true From 65c1b909b4350397f002d45228ab7cd77433de04 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:07:32 +0200 Subject: [PATCH 32/65] Remove unnecessary Result types --- .../src/generator/builder_generator.rs | 25 +++--- .../src/generator/data_generator.rs | 22 ++--- .../src/generator/field_generator.rs | 13 ++- .../src/generator/group_generator.rs | 30 +++++++ .../src/generator/mod.rs | 11 +-- .../src/info/field_info.rs | 24 +++--- .../src/info/struct_info.rs | 81 ++++++++++--------- const_typed_builder_derive/src/lib.rs | 17 ++-- 8 files changed, 124 insertions(+), 99 deletions(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 6766c8a..e182149 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -2,10 +2,7 @@ use super::{ field_generator::FieldGenerator, generics_generator::GenericsGenerator, group_generator::GroupGenerator, }; -use crate::{ - info::{FieldKind, SolveType}, - StreamResult, VecStreamResult, -}; +use crate::info::{FieldKind, SolveType}; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -72,14 +69,14 @@ impl<'a> BuilderGenerator<'a> { /// # Returns /// /// A `StreamResult` representing the generated code for the builder struct and methods. - pub fn generate(&self) -> StreamResult { + pub fn generate(&self) -> TokenStream { let builder_struct = self.generate_struct(); - let builder_impl = self.generate_impl()?; + let builder_impl = self.generate_impl(); let tokens = quote!( #builder_struct #builder_impl ); - Ok(tokens) + tokens } /// Generates the code for the builder struct definition. @@ -103,8 +100,8 @@ impl<'a> BuilderGenerator<'a> { } /// Generates the implementation code for the builder struct's `new`, `build` and setter methods. - fn generate_impl(&self) -> StreamResult { - let builder_setters = self.generate_setters_impl()?; + fn generate_impl(&self) -> TokenStream { + let builder_setters = self.generate_setters_impl(); let builder_new = self.generate_new_impl(); let builder_build = self.generate_build_impl(); @@ -113,7 +110,7 @@ impl<'a> BuilderGenerator<'a> { #builder_setters #builder_build ); - Ok(tokens) + tokens } /// Generates the code for the `new` method implementation. @@ -208,7 +205,7 @@ impl<'a> BuilderGenerator<'a> { } /// Generates the code for the setter methods of the builder. - fn generate_setters_impl(&self) -> StreamResult { + fn generate_setters_impl(&self) -> TokenStream { let builder_name = self.builder_name; let data_field = self.data_field_ident(); let setters = self @@ -237,12 +234,12 @@ impl<'a> BuilderGenerator<'a> { } } ); - Ok(tokens) - }).collect::()?; + tokens + }); let tokens = quote!( #(#setters)* ); - Ok(tokens) + tokens } } diff --git a/const_typed_builder_derive/src/generator/data_generator.rs b/const_typed_builder_derive/src/generator/data_generator.rs index ef0ab22..9722aa1 100644 --- a/const_typed_builder_derive/src/generator/data_generator.rs +++ b/const_typed_builder_derive/src/generator/data_generator.rs @@ -1,5 +1,5 @@ use super::{field_generator::FieldGenerator, generics_generator::GenericsGenerator}; -use crate::StreamResult; +use proc_macro2::TokenStream; use quote::quote; /// The `DataGenerator` struct is responsible for generating code related to the data struct @@ -43,23 +43,23 @@ impl<'a> DataGenerator<'a> { /// # Returns /// /// A `StreamResult` representing the generated code for the data struct and conversions. - pub fn generate(&self) -> StreamResult { - let data_struct = self.generate_struct()?; - let data_impl = self.generate_impl()?; + pub fn generate(&self) -> TokenStream { + let data_struct = self.generate_struct(); + let data_impl = self.generate_impl(); let tokens = quote!( #data_struct #data_impl ); - Ok(tokens) + tokens } /// Generates the implementation code for conversions between the data struct and the target struct. - fn generate_impl(&self) -> StreamResult { + fn generate_impl(&self) -> TokenStream { let data_name = self.data_name; let struct_name = self.target_name; - let from_fields = self.field_gen.data_impl_from_fields()?; + let from_fields = self.field_gen.data_impl_from_fields(); let def_fields = self.field_gen.data_impl_default_fields(); let (impl_generics, type_generics, where_clause) = @@ -82,14 +82,14 @@ impl<'a> DataGenerator<'a> { } } ); - Ok(tokens) + tokens } /// Generates the code for the data struct itself. - fn generate_struct(&self) -> StreamResult { + fn generate_struct(&self) -> TokenStream { let data_name = self.data_name; - let fields = self.field_gen.data_struct_fields()?; + let fields = self.field_gen.data_struct_fields(); let (impl_generics, _type_generics, where_clause) = self.generics_gen.target_generics().split_for_impl(); @@ -98,6 +98,6 @@ impl<'a> DataGenerator<'a> { #(#fields),* } ); - Ok(tokens) + tokens } } diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index 9ca2c8d..daf6477 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -1,7 +1,4 @@ -use crate::{ - info::{FieldInfo, FieldKind}, - VecStreamResult, -}; +use crate::info::{FieldInfo, FieldKind}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -35,7 +32,7 @@ impl<'a> FieldGenerator<'a> { /// # Returns /// /// A `VecStreamResult` representing the generated code for the data struct fields. - pub fn data_struct_fields(&self) -> VecStreamResult { + pub fn data_struct_fields(&self) -> Vec { self.fields .iter() .filter_map(|field| { @@ -55,7 +52,7 @@ impl<'a> FieldGenerator<'a> { let tokens = quote!( pub #field_name: #data_field_type ); - Some(Ok(tokens)) + Some(tokens) }) .collect() } @@ -65,7 +62,7 @@ impl<'a> FieldGenerator<'a> { /// # Returns /// /// A `VecStreamResult` representing the generated code for the `From` trait implementation. - pub fn data_impl_from_fields(&self) -> VecStreamResult { + pub fn data_impl_from_fields(&self) -> Vec { self.fields .iter() .map(|field| { @@ -82,7 +79,7 @@ impl<'a> FieldGenerator<'a> { quote!(#field_name: data.#field_name.unwrap()) } }; - Ok(tokens) + tokens }) .collect() } diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index a89d914..94b7e3d 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -4,6 +4,7 @@ use crate::{ }; use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; +use proc_macro_error::{emit_error, emit_warning}; use quote::{format_ident, quote}; use std::collections::BTreeSet; @@ -24,9 +25,38 @@ impl<'a> GroupGenerator<'a> { /// /// A `GroupGenerator` instance initialized with the provided groups. pub fn new(groups: Vec<&'a GroupInfo>) -> Self { + Self::can_be_valid(&groups); Self { groups } } + fn can_be_valid(groups: &[&'a GroupInfo]) -> () { + groups.iter().for_each(|group| { + let associated_count = group.indices().len(); + match group.group_type() { + crate::info::GroupType::Exact(expected) => { + if associated_count < *expected { + emit_error!(group.name(), "Group can never be satisfied"); + } else if associated_count == *expected { + emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized. Consider removing group and using [builder(mandatory)] instead"); + } + }, + crate::info::GroupType::AtLeast(expected) => { + if associated_count < *expected { + emit_error!(group.name(), "Group cannot be satisfied"); + } + if *expected == 0 { + emit_warning!(group.name(), "Group has no effect. Consider removing the group") + } + }, + crate::info::GroupType::AtMost(expected) => { + if *expected == 0 { + emit_warning!(group.name(), "Group can only be satisfied if none of the fields are initialized. Consider removing group and using [builder(skip)] instead"); + } + }, + } + }); + } + /// Returns all valid combinations of the const generics for the grouped fields /// /// # Returns diff --git a/const_typed_builder_derive/src/generator/mod.rs b/const_typed_builder_derive/src/generator/mod.rs index 1953f64..7e1bcc9 100644 --- a/const_typed_builder_derive/src/generator/mod.rs +++ b/const_typed_builder_derive/src/generator/mod.rs @@ -10,7 +10,8 @@ use self::{ field_generator::FieldGenerator, generics_generator::GenericsGenerator, group_generator::GroupGenerator, target_generator::TargetGenerator, }; -use crate::{info::StructInfo, StreamResult}; +use crate::info::StructInfo; +use proc_macro2::TokenStream; use quote::quote; /// The `Generator` struct is responsible for generating code for the builder pattern based on the provided `StructInfo`. @@ -64,15 +65,15 @@ impl<'a> Generator<'a> { /// # Returns /// /// A `StreamResult` representing the generated token stream. - pub fn generate(&self) -> StreamResult { + pub fn generate(&self) -> TokenStream { let target = self.target_gen.generate(); - let data = self.data_gen.generate()?; - let builder = self.builder_gen.generate()?; + let data = self.data_gen.generate(); + let builder = self.builder_gen.generate(); let tokens = quote!( #target #builder #data ); - Ok(tokens) + tokens } } diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index af45bc5..ff34fb6 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -4,6 +4,7 @@ use crate::{ CONST_IDENT_PREFIX, }; use proc_macro2::Span; +use proc_macro_error::emit_error; use quote::format_ident; use std::collections::HashSet; use syn::{ExprPath, Token}; @@ -47,7 +48,7 @@ impl<'a> FieldInfo<'a> { field: &'a syn::Field, struct_settings: &mut StructSettings, index: usize, - ) -> syn::Result { + ) -> Option { if let syn::Field { attrs, ident: Some(ident), @@ -61,7 +62,8 @@ impl<'a> FieldInfo<'a> { .default_field_settings() .clone() .with_ty(ty) - .with_attrs(attrs)?; + .with_attrs(attrs) + .ok()?; let info = if settings.skipped { Self { @@ -82,10 +84,12 @@ impl<'a> FieldInfo<'a> { } } else if !settings.groups.is_empty() { for group_name in settings.groups { - struct_settings - .group_by_name_mut(&group_name.to_string()) - .ok_or(syn::Error::new_spanned(group_name, "Can't find group"))? - .associate(index); + if let Some(group) = struct_settings.group_by_name_mut(&group_name.to_string()) + { + group.associate(index); + } else { + emit_error!(group_name, "Can't find group"); + } } Self { @@ -105,12 +109,10 @@ impl<'a> FieldInfo<'a> { } }; - Ok(info) + Some(info) } else { - Err(syn::Error::new_spanned( - field, - "Unnamed fields are not supported", - )) + emit_error!(field, "Unnamed fields are not supported",); + None } } diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 9ea313d..0d55834 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -4,6 +4,7 @@ use crate::symbol::{ ASSUME_MANDATORY, AT_LEAST, AT_MOST, BRUTE_FORCE, BUILDER, COMPILER, EXACT, GROUP, SINGLE, SOLVER, }; +use proc_macro_error::emit_error; use quote::format_ident; use std::collections::{BTreeSet, HashMap}; use syn::Token; @@ -47,49 +48,49 @@ impl<'a> StructInfo<'a> { /// # Returns /// /// A `syn::Result` containing the `StructInfo` instance if successful, - pub fn new(ast: &'a syn::DeriveInput) -> syn::Result { - if let syn::DeriveInput { - attrs, - vis, - ident, - generics, - data: - syn::Data::Struct(syn::DataStruct { - fields: syn::Fields::Named(fields), - .. - }), - } = &ast - { - if fields.named.is_empty() { - return Err(syn::Error::new_spanned(fields, "No fields found")); - } + pub fn new(ast: &'a syn::DeriveInput) -> Option { + match &ast { + syn::DeriveInput { + attrs, + vis, + ident, + generics, + data: + syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(fields), + .. + }), + } => { + if fields.named.is_empty() { + emit_error!(fields, "No fields found"); + } - let mut settings = StructSettings::new().with_attrs(attrs)?; + let mut settings = StructSettings::new().with_attrs(attrs).ok()?; - let field_infos = fields - .named - .iter() - .enumerate() - .map(|(index, field)| FieldInfo::new(field, &mut settings, index)) - .collect::>>()?; + let field_infos = fields + .named + .iter() + .enumerate() + .map(|(index, field)| FieldInfo::new(field, &mut settings, index)) + .collect::>>()?; - let info = StructInfo { - ident, - vis, - generics, - builder_ident: format_ident!("{}{}", ident, settings.builder_suffix), - data_ident: format_ident!("{}{}", ident, settings.data_suffix), - _mandatory_indices: settings.mandatory_indices, - groups: settings.groups, - field_infos, - solve_type: settings.solver_type, - }; - Ok(info) - } else { - Err(syn::Error::new_spanned( - ast, - "Builder is only supported for named structs", - )) + let info = StructInfo { + ident, + vis, + generics, + builder_ident: format_ident!("{}{}", ident, settings.builder_suffix), + data_ident: format_ident!("{}{}", ident, settings.data_suffix), + _mandatory_indices: settings.mandatory_indices, + groups: settings.groups, + field_infos, + solve_type: settings.solver_type, + }; + Some(info) + } + _ => { + emit_error!(ast, "Builder is only supported for named structs",); + None + } } } diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index a2569ff..f9ffce9 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -5,13 +5,10 @@ mod symbol; use generator::Generator; use info::StructInfo; use proc_macro2::TokenStream; +use proc_macro_error::proc_macro_error; +use quote::quote; use syn::DeriveInput; -/// A type alias for the result of a token stream operation. -type StreamResult = syn::Result; -/// A type alias for the result of a vector of token streams. -type VecStreamResult = syn::Result>; - const CONST_IDENT_PREFIX: &str = "__BUILDER_CONST"; /// The `derive_builder` macro is used to automatically generate builder @@ -31,11 +28,11 @@ const CONST_IDENT_PREFIX: &str = "__BUILDER_CONST"; /// This will generate a builder pattern for `MyStruct`, allowing you to /// construct instances of `MyStruct` with a fluent API. #[proc_macro_derive(Builder, attributes(builder, group))] +#[proc_macro_error] pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse_macro_input!(input as DeriveInput); - impl_my_derive(&ast) - .unwrap_or_else(syn::Error::into_compile_error) - .into() + let stream = impl_my_derive(&ast); + quote!(#stream).into() } /// This function implements the custom derive for the `Builder` trait. @@ -52,8 +49,8 @@ pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// # Returns /// /// A `StreamResult` representing the generated token stream. -fn impl_my_derive(ast: &syn::DeriveInput) -> StreamResult { +fn impl_my_derive(ast: &syn::DeriveInput) -> Option { let struct_info = StructInfo::new(ast)?; let generator = Generator::new(&struct_info); - generator.generate() + Some(generator.generate()) } From 5b26d47308602fd42dc26a8cdfbb0effdf4fb6d1 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:07:43 +0200 Subject: [PATCH 33/65] Update tests --- const_typed_builder_test/compile_fail/group_1.rs | 2 +- .../compile_fail/group_1.stderr | 13 +++++-------- const_typed_builder_test/compile_fail/group_2.rs | 13 +++++++++++++ .../compile_fail/group_2.stderr | 11 +++++++++++ .../compile_fail/group_solver_compiler_1.rs | 2 ++ .../compile_fail/group_solver_compiler_1.stderr | 11 +++++++---- 6 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 const_typed_builder_test/compile_fail/group_2.rs create mode 100644 const_typed_builder_test/compile_fail/group_2.stderr diff --git a/const_typed_builder_test/compile_fail/group_1.rs b/const_typed_builder_test/compile_fail/group_1.rs index eb5aa5a..4ca314f 100644 --- a/const_typed_builder_test/compile_fail/group_1.rs +++ b/const_typed_builder_test/compile_fail/group_1.rs @@ -7,5 +7,5 @@ fn main() { #[builder(group = baz)] bar: Option, } - let foo = Foo::builder().bar("Hello world!".to_string()).build(); + let foobuilder = Foo::builder().bar("Hello world!".to_string()); } \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_1.stderr b/const_typed_builder_test/compile_fail/group_1.stderr index 7f13c2a..637aa87 100644 --- a/const_typed_builder_test/compile_fail/group_1.stderr +++ b/const_typed_builder_test/compile_fail/group_1.stderr @@ -1,8 +1,5 @@ -error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope - --> ./compile_fail/group_1.rs:10:62 - | -4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] - | ------- method `build` not found for this struct -... -10 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); - | ^^^^^ method not found in `FooBuilder` +error: Group cannot be satisfied + --> ./compile_fail/group_1.rs:5:13 + | +5 | #[group(baz = at_least(2))] + | ^^^ diff --git a/const_typed_builder_test/compile_fail/group_2.rs b/const_typed_builder_test/compile_fail/group_2.rs new file mode 100644 index 0000000..f2cd9ca --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_2.rs @@ -0,0 +1,13 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(baz = at_least(2))] + pub struct Foo { + #[builder(group = baz)] + bar: Option, + #[builder(group = baz)] + baz: Option, + } + let foo = Foo::builder().bar("Hello world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_2.stderr b/const_typed_builder_test/compile_fail/group_2.stderr new file mode 100644 index 0000000..4177a8a --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_2.stderr @@ -0,0 +1,11 @@ +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_2.rs:12:62 + | +4 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ------- method `build` not found for this struct +... +12 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` + | + = note: the method was found for + - `FooBuilder` diff --git a/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs b/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs index bbf4cea..3292e7a 100644 --- a/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs +++ b/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs @@ -8,6 +8,8 @@ fn main() { pub struct Foo { #[builder(group = baz)] bar: Option, + #[builder(group = baz)] + baz: Option } let foo = Foo::builder().bar("Hello world!".to_string()).build(); } \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr b/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr index 6e623b9..b38b4ad 100644 --- a/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr +++ b/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr @@ -4,11 +4,14 @@ error: cannot find attribute `build` in this scope 7 | #[build(solver = compiler)] | ^^^^^ -error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope - --> ./compile_fail/group_solver_compiler_1.rs:12:62 +error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope + --> ./compile_fail/group_solver_compiler_1.rs:14:62 | 5 | #[derive(Debug, Default, PartialEq, Eq, Builder)] | ------- method `build` not found for this struct ... -12 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); - | ^^^^^ method not found in `FooBuilder` +14 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); + | ^^^^^ method not found in `FooBuilder` + | + = note: the method was found for + - `FooBuilder` From 7382a97103a8f7d02ef5e1da9922ff28a32258a5 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:58:39 +0200 Subject: [PATCH 34/65] Change handle_attribute return type to () --- .../src/info/field_info.rs | 168 ++++++++-------- .../src/info/struct_info.rs | 184 ++++++++++-------- 2 files changed, 195 insertions(+), 157 deletions(-) diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index ff34fb6..3c0d5be 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -228,10 +228,8 @@ impl FieldSettings { /// /// A `syn::Result` indicating success or failure of attribute handling. fn with_attrs(mut self, attrs: &[syn::Attribute]) -> syn::Result { - attrs - .iter() - .map(|attr| self.handle_attribute(attr)) - .collect::, _>>()?; + attrs.iter().for_each(|attr| self.handle_attribute(attr)); + // .collect::, _>>()?; Ok(self) } @@ -279,103 +277,115 @@ impl FieldSettings { /// # Returns /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. - fn handle_attribute(&mut self, attr: &syn::Attribute) -> syn::Result<()> { + fn handle_attribute(&mut self, attr: &syn::Attribute) { if let Some(ident) = attr.path().get_ident() { if ident != BUILDER { - return Ok(()); + return; } } - let list = attr.meta.require_list()?; - if list.tokens.is_empty() { - return Ok(()); + match attr.meta.require_list() { + Ok(list) => { + if list.tokens.is_empty() { + return; + } + } + Err(err) => emit_error!(attr, err), + } + if let Err(err) = attr.meta.require_list() { + emit_error!(attr, err) } attr.parse_nested_meta(|meta| { - if meta.path == SKIP { - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Bool(syn::LitBool { value, .. }), - .. - }) = expr - { - self.skipped = value; - } - } else { - self.skipped = true; + let path_ident = match meta.path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!(&attr.meta, err); + return Ok(()); } - } - if meta.path == MANDATORY { - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Bool(syn::LitBool { value, .. }), - .. - }) = expr - { - self.mandatory = value; + }; + + match (&path_ident.to_string()).into() { + SKIP => { + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(syn::LitBool { value, .. }), + .. + }) = expr + { + self.skipped = value; + } + } else { + self.skipped = true; } - } else { - self.mandatory = true; } - } - if meta.path == OPTIONAL { - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Bool(syn::LitBool { value, .. }), - .. - }) = expr - { - self.mandatory = !value; + MANDATORY => { + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(syn::LitBool { value, .. }), + .. + }) = expr + { + self.mandatory = value; + } + } else { + self.mandatory = true; } - } else { - self.mandatory = false; } - } - if meta.path == GROUP { - if self.mandatory { - return Err(syn::Error::new_spanned( - &meta.path, - "Only optionals in group", - )); + OPTIONAL => { + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(syn::LitBool { value, .. }), + .. + }) = expr + { + self.mandatory = !value; + } + } else { + self.mandatory = false; + } } - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Path(ExprPath { path, .. }) = &expr { - let group_name = path - .get_ident() - .ok_or(syn::Error::new_spanned(path, "Can't parse group"))?; + GROUP => { + if self.mandatory { + emit_error!(&meta.path, "Only optionals in group",); + } + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + if let syn::Expr::Path(ExprPath { path, .. }) = &expr { + let group_name = path + .get_ident() + .ok_or(syn::Error::new_spanned(path, "Can't parse group"))?; - if !self.groups.insert(group_name.clone()) { - return Err(syn::Error::new_spanned( - &expr, - "Multiple adds to the same group", - )); + if !self.groups.insert(group_name.clone()) { + emit_error!(&expr, "Multiple adds to the same group",); + } } - } - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }) = &expr - { - if !self - .groups - .insert(syn::Ident::new(lit.value().as_str(), lit.span())) + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }) = &expr { - return Err(syn::Error::new_spanned( - &expr, - "Multiple adds to the same group", - )); + if !self + .groups + .insert(syn::Ident::new(lit.value().as_str(), lit.span())) + { + emit_error!(&expr, "Multiple adds to the same group",); + } } } } - } - if meta.path == PROPAGATE { - self.propagate = true; + PROPAGATE => { + self.propagate = true; + } + _ => { + emit_error!(&attr.meta, "Unknown attribute") + } } Ok(()) }) + .unwrap() } } diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 0d55834..7e59549 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -65,7 +65,7 @@ impl<'a> StructInfo<'a> { emit_error!(fields, "No fields found"); } - let mut settings = StructSettings::new().with_attrs(attrs).ok()?; + let mut settings = StructSettings::new().with_attrs(attrs); let field_infos = fields .named @@ -190,12 +190,9 @@ impl StructSettings { /// # Returns /// /// A `syn::Result` indicating success or failure of attribute handling. - pub fn with_attrs(mut self, attrs: &[syn::Attribute]) -> syn::Result { - attrs - .iter() - .map(|attr| self.handle_attribute(attr)) - .collect::, _>>()?; - Ok(self) + pub fn with_attrs(mut self, attrs: &[syn::Attribute]) -> Self { + attrs.iter().for_each(|attr| self.handle_attribute(attr)); + self } /// Handles the parsing and processing of attributes applied to a struct. @@ -209,17 +206,13 @@ impl StructSettings { /// # Returns /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. - fn handle_attribute(&mut self, attr: &syn::Attribute) -> syn::Result<()> { + fn handle_attribute(&mut self, attr: &syn::Attribute) { if let Some(ident) = attr.path().get_ident() { if ident == GROUP { - self.handle_group_attribute(attr) + self.handle_group_attribute(attr); } else if ident == BUILDER { - self.handle_builder_attribute(attr) - } else { - Ok(()) + self.handle_builder_attribute(attr); } - } else { - Ok(()) } } @@ -243,11 +236,15 @@ impl StructSettings { /// # Returns /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. - fn handle_builder_attribute(&mut self, attr: &syn::Attribute) -> syn::Result<()> { - let list = attr.meta.require_list()?; - if list.tokens.is_empty() { - return Ok(()); - } + fn handle_builder_attribute(&mut self, attr: &syn::Attribute) { + match attr.meta.require_list() { + Ok(list) => { + if list.tokens.is_empty() { + return; + } + } + Err(err) => emit_error!(attr, err), + }; attr.parse_nested_meta(|meta| { if meta.path == ASSUME_MANDATORY { @@ -268,23 +265,25 @@ impl StructSettings { if meta.input.peek(Token![=]) { let expr: syn::Expr = meta.value()?.parse()?; if let syn::Expr::Path(syn::ExprPath { path, .. }) = expr { - let solve_type = path - .get_ident() - .ok_or_else(|| syn::Error::new_spanned(&path, "Can't parse solver"))?; - match (&solve_type.to_string()).into() { - BRUTE_FORCE => self.solver_type = SolveType::BruteForce, - COMPILER => self.solver_type = SolveType::Compiler, - _ => Err(syn::Error::new_spanned(&path, "Unknown solver type"))?, + if let Some(solve_type) = path.get_ident() { + match (&solve_type.to_string()).into() { + BRUTE_FORCE => self.solver_type = SolveType::BruteForce, + COMPILER => self.solver_type = SolveType::Compiler, + _ => emit_error!(&path, "Unknown solver type"), + } + } else { + emit_error!(meta.path, "Can't parse solver specification"); } } else { - Err(syn::Error::new_spanned(meta.path, "Can't parse solver"))?; + emit_error!(meta.path, "Can't parse solver specification"); } } else { - Err(syn::Error::new_spanned(meta.path, "Can't parse solver"))?; + emit_error!(meta.path, "Solver type needs to be specified"); } } Ok(()) }) + .unwrap() } /// Handles the parsing and processing of group attributes applied to a struct. @@ -307,73 +306,102 @@ impl StructSettings { /// # Returns /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. - fn handle_group_attribute(&mut self, attr: &syn::Attribute) -> syn::Result<()> { - let list = attr.meta.require_list()?; - if list.tokens.is_empty() { - return Ok(()); - } + fn handle_group_attribute(&mut self, attr: &syn::Attribute) { + match attr.meta.require_list() { + Ok(list) => { + if list.tokens.is_empty() { + return; + } + } + Err(err) => emit_error!(attr, err), + }; attr.parse_nested_meta(|meta| { - let group_name = meta - .path - .get_ident() - .ok_or_else(|| syn::Error::new_spanned(&attr.meta, "Can't parse group name"))? - .clone(); - - let expr: syn::Expr = meta.value()?.parse()?; + let group_name = match meta.path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!(&attr.meta, err); + return Ok(()); + } + }; - let group_type = match &expr { + let group_type = match meta.value()?.parse()? { syn::Expr::Call(syn::ExprCall { func, args, .. }) => { - let group_type = if let syn::Expr::Path(syn::ExprPath { path, .. }) = - func.as_ref() - { - path.get_ident() - .ok_or_else(|| syn::Error::new_spanned(func, "Can't parse group type")) - } else { - Err(syn::Error::new_spanned(func, "Can't find group type")) - }?; + let group_type = match func.as_ref() { + syn::Expr::Path(syn::ExprPath { path, .. }) => match path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!(&attr.meta, err); + return Ok(()); + } + }, + _ => { + emit_error!(&attr.meta, "Can't find group type"); + return Ok(()); + } + }; - let group_args = if let Some(syn::Expr::Lit(syn::ExprLit { - attrs: _, - lit: syn::Lit::Int(val), - })) = args.first() - { - val.base10_parse::() - } else { - Err(syn::Error::new_spanned(func, "Can't parse group args")) - }?; - match (&group_type.to_string()).into() { - EXACT => Ok(GroupType::Exact(group_args)), - AT_LEAST => Ok(GroupType::AtLeast(group_args)), - AT_MOST => Ok(GroupType::AtMost(group_args)), - SINGLE => Err(syn::Error::new_spanned( - args, - "`single` doesn't take any arguments", - )), - _ => Err(syn::Error::new_spanned(group_type, "Unknown group type")), + match args.first() { + Some(syn::Expr::Lit(syn::ExprLit { + attrs: _, + lit: syn::Lit::Int(val), + })) => match val.base10_parse::() { + Ok(group_args) => match (&group_type.to_string()).into() { + EXACT => GroupType::Exact(group_args), + AT_LEAST => GroupType::AtLeast(group_args), + AT_MOST => GroupType::AtMost(group_args), + SINGLE => { + emit_error!(args, "`single` doesn't take any arguments",); + return Ok(()); + } + _ => { + emit_error!(group_type, "Unknown group type"); + return Ok(()); + } + }, + Err(err) => { + emit_error!(val, err); + return Ok(()); + } + }, + _ => { + emit_error!(func, "Can't parse group args"); + return Ok(()); + } } } syn::Expr::Path(syn::ExprPath { path, .. }) => { - let group_type = path - .get_ident() - .ok_or_else(|| syn::Error::new_spanned(path, "Can't parse group type"))?; + let group_type = match path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!(path, err); + return Ok(()); + } + }; match (&group_type.to_string()).into() { - EXACT | AT_LEAST | AT_MOST => Err(syn::Error::new_spanned( - &attr.meta, - "Missing arguments for group type", - )), - SINGLE => Ok(GroupType::Exact(1)), - _ => Err(syn::Error::new_spanned(&attr.meta, "Can't parse group")), + EXACT | AT_LEAST | AT_MOST => { + emit_error!(&attr.meta, "Missing arguments for group type"); + return Ok(()); + } + SINGLE => GroupType::Exact(1), + _ => { + emit_error!(&attr.meta, "Can't parse group"); + return Ok(()); + } } } - _ => Err(syn::Error::new_spanned(&attr.meta, "Can't parse group")), + _ => { + emit_error!(&attr.meta, "Can't find group type"); + return Ok(()); + } }; self.groups.insert( group_name.to_string(), - GroupInfo::new(group_name, group_type?), + GroupInfo::new(group_name.clone(), group_type), ); Ok(()) }) + .unwrap() } } From 023e721e29439ae727ca7172ce4c6d13ed5eb626 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:58:49 +0200 Subject: [PATCH 35/65] Satisfy clippy --- .../src/generator/group_generator.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index 94b7e3d..e158470 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -29,15 +29,15 @@ impl<'a> GroupGenerator<'a> { Self { groups } } - fn can_be_valid(groups: &[&'a GroupInfo]) -> () { + fn can_be_valid(groups: &[&'a GroupInfo]) { groups.iter().for_each(|group| { let associated_count = group.indices().len(); match group.group_type() { crate::info::GroupType::Exact(expected) => { - if associated_count < *expected { - emit_error!(group.name(), "Group can never be satisfied"); - } else if associated_count == *expected { - emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized. Consider removing group and using [builder(mandatory)] instead"); + match associated_count.cmp(expected) { + std::cmp::Ordering::Less => emit_error!(group.name(), "Group can never be satisfied"), + std::cmp::Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized. Consider removing group and using [builder(mandatory)] instead"), + std::cmp::Ordering::Greater => {}, } }, crate::info::GroupType::AtLeast(expected) => { From 9f2407f8139f34ee43a1203751c4b1cb952773ed Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:20:29 +0200 Subject: [PATCH 36/65] Change unwrap to unwrap_or_else --- const_typed_builder_derive/src/info/field_info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index 3c0d5be..b0ad89c 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -385,7 +385,7 @@ impl FieldSettings { } Ok(()) }) - .unwrap() + .unwrap_or_else(|err| emit_error!(&attr.meta, err)) } } From 88c86ebfc025c5b8ab50a81059ac441d3785bcc4 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:20:56 +0200 Subject: [PATCH 37/65] Make more use of match statements in handle_attribute --- .../src/info/struct_info.rs | 83 ++++++++++++------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 7e59549..0fa2fc4 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -207,12 +207,17 @@ impl StructSettings { /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) { - if let Some(ident) = attr.path().get_ident() { - if ident == GROUP { - self.handle_group_attribute(attr); - } else if ident == BUILDER { - self.handle_builder_attribute(attr); + let path_ident = match attr.path().require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!(attr.path(), err); + return; } + }; + match (&path_ident.to_string()).into() { + GROUP => self.handle_group_attribute(attr), + BUILDER => self.handle_builder_attribute(attr), + _ => emit_error!(&attr.meta, "Unknown attribute"), } } @@ -247,43 +252,56 @@ impl StructSettings { }; attr.parse_nested_meta(|meta| { - if meta.path == ASSUME_MANDATORY { - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Bool(syn::LitBool { value, .. }), - .. - }) = expr - { - self.default_field_settings.mandatory = value; + let path_ident = match meta.path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!(&attr.meta, err); + return Ok(()); + } + }; + + match (&path_ident.to_string()).into() { + ASSUME_MANDATORY => { + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(syn::LitBool { value, .. }), + .. + }) = expr + { + self.default_field_settings.mandatory = value; + } + } else { + self.default_field_settings.mandatory = true; } - } else { - self.default_field_settings.mandatory = true; } - } - if meta.path == SOLVER { - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Path(syn::ExprPath { path, .. }) = expr { - if let Some(solve_type) = path.get_ident() { - match (&solve_type.to_string()).into() { - BRUTE_FORCE => self.solver_type = SolveType::BruteForce, - COMPILER => self.solver_type = SolveType::Compiler, - _ => emit_error!(&path, "Unknown solver type"), + SOLVER => { + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + if let syn::Expr::Path(syn::ExprPath { path, .. }) = expr { + if let Some(solve_type) = path.get_ident() { + match (&solve_type.to_string()).into() { + BRUTE_FORCE => self.solver_type = SolveType::BruteForce, + COMPILER => self.solver_type = SolveType::Compiler, + _ => emit_error!(&path, "Unknown solver type"), + } + } else { + emit_error!(meta.path, "Can't parse solver specification"); } } else { emit_error!(meta.path, "Can't parse solver specification"); } } else { - emit_error!(meta.path, "Can't parse solver specification"); + emit_error!(meta.path, "Solver type needs to be specified"); } - } else { - emit_error!(meta.path, "Solver type needs to be specified"); + } + _ => { + emit_error!(meta.path, "Unknown attribute"); } } Ok(()) }) - .unwrap() + .unwrap_or_else(|err| emit_error!(&attr.meta, err)) } /// Handles the parsing and processing of group attributes applied to a struct. @@ -364,8 +382,9 @@ impl StructSettings { return Ok(()); } }, + _ => { - emit_error!(func, "Can't parse group args"); + emit_error!(func, "Can't parse group argument"); return Ok(()); } } @@ -402,6 +421,6 @@ impl StructSettings { ); Ok(()) }) - .unwrap() + .unwrap_or_else(|err| emit_error!(&attr.meta, err)) } } From db1bfed5a642bad86b37c8b7171f0257c3d61087 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:21:08 +0200 Subject: [PATCH 38/65] Fix faulty test --- .../compile_fail/group_solver_compiler_1.rs | 2 +- .../group_solver_compiler_1.stderr | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs b/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs index 3292e7a..da8d080 100644 --- a/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs +++ b/const_typed_builder_test/compile_fail/group_solver_compiler_1.rs @@ -4,7 +4,7 @@ use const_typed_builder::Builder; fn main() { #[derive(Debug, Default, PartialEq, Eq, Builder)] #[group(baz = at_least(2))] - #[build(solver = compiler)] + #[builder(solver = compiler)] pub struct Foo { #[builder(group = baz)] bar: Option, diff --git a/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr b/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr index b38b4ad..5adf77a 100644 --- a/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr +++ b/const_typed_builder_test/compile_fail/group_solver_compiler_1.stderr @@ -1,17 +1,21 @@ -error: cannot find attribute `build` in this scope - --> ./compile_fail/group_solver_compiler_1.rs:7:7 +warning: unused variable: `foo` + --> ./compile_fail/group_solver_compiler_1.rs:14:9 + | +14 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); + | ^^^ help: if this is intentional, prefix it with an underscore: `_foo` + | + = note: `#[warn(unused_variables)]` on by default + +error[E0080]: evaluation of `main::FooBuilder::::GROUP_VERIFIER` failed + --> ./compile_fail/group_solver_compiler_1.rs:5:45 + | +5 | #[derive(Debug, Default, PartialEq, Eq, Builder)] + | ^^^^^^^ the evaluated program panicked at '`.build()` failed because the bounds of group `baz` where not met (at_least 2)', $DIR/./compile_fail/group_solver_compiler_1.rs:5:45 | -7 | #[build(solver = compiler)] - | ^^^^^ + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0599]: no method named `build` found for struct `FooBuilder` in the current scope - --> ./compile_fail/group_solver_compiler_1.rs:14:62 +note: the above error was encountered while instantiating `fn main::FooBuilder::::build` + --> ./compile_fail/group_solver_compiler_1.rs:14:15 | -5 | #[derive(Debug, Default, PartialEq, Eq, Builder)] - | ------- method `build` not found for this struct -... 14 | let foo = Foo::builder().bar("Hello world!".to_string()).build(); - | ^^^^^ method not found in `FooBuilder` - | - = note: the method was found for - - `FooBuilder` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 24144bbee6d48634b9bba051e9a8ba44134c7fdc Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:22:02 +0200 Subject: [PATCH 39/65] Fixme already warns but should actually fail or warn that the group attribute is ignored --- const_typed_builder_test/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index 1d0b4b7..ba32b68 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -370,7 +370,7 @@ mod test { .build(); assert_eq!(expected, foo); - // FIXME: Should fail or warn + // FIXME: Should fail #[derive(Debug, Default, PartialEq, Eq, Builder)] #[group(quz = single)] pub struct Nope { From 31443504e81f548b6eb50abe3327599bd47dbb7b Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:12:31 +0200 Subject: [PATCH 40/65] Make group and mandatory mutually exclusive --- .../src/info/field_info.rs | 69 +++++++++++-------- .../src/info/struct_info.rs | 42 +++++------ .../compile_fail/group_and_mandatory_3.rs | 16 +++++ .../compile_fail/group_and_mandatory_3.stderr | 11 +++ .../compile_fail/group_and_mandatory_4.rs | 16 +++++ .../compile_fail/group_and_mandatory_4.stderr | 11 +++ const_typed_builder_test/src/lib.rs | 13 ---- 7 files changed, 109 insertions(+), 69 deletions(-) create mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_3.rs create mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr create mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_4.rs create mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index b0ad89c..d595d8f 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -4,7 +4,7 @@ use crate::{ CONST_IDENT_PREFIX, }; use proc_macro2::Span; -use proc_macro_error::emit_error; +use proc_macro_error::{emit_error, emit_warning}; use quote::format_ident; use std::collections::HashSet; use syn::{ExprPath, Token}; @@ -278,22 +278,24 @@ impl FieldSettings { /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) { - if let Some(ident) = attr.path().get_ident() { - if ident != BUILDER { - return; + match attr.path().require_ident() { + Ok(ident) if ident == BUILDER => {}, + Ok(ident) => { + emit_error!(ident, format!("{ident} can't be used as a top level field attribute")); + } + Err(err) => { + emit_error!(attr.path(), err); } } + match attr.meta.require_list() { Ok(list) => { if list.tokens.is_empty() { - return; + emit_warning!(list, "Empty atrribute list"); } } Err(err) => emit_error!(attr, err), } - if let Err(err) = attr.meta.require_list() { - emit_error!(attr, err) - } attr.parse_nested_meta(|meta| { let path_ident = match meta.path.require_ident() { @@ -348,31 +350,34 @@ impl FieldSettings { } } GROUP => { - if self.mandatory { - emit_error!(&meta.path, "Only optionals in group",); - } if meta.input.peek(Token![=]) { let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Path(ExprPath { path, .. }) = &expr { - let group_name = path - .get_ident() - .ok_or(syn::Error::new_spanned(path, "Can't parse group"))?; + match expr { + syn::Expr::Path(ExprPath { path, .. }) => { + let group_name = match path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!(path, err); + return Ok(()); + } + }; - if !self.groups.insert(group_name.clone()) { - emit_error!(&expr, "Multiple adds to the same group",); - } - } - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }) = &expr - { - if !self - .groups - .insert(syn::Ident::new(lit.value().as_str(), lit.span())) - { - emit_error!(&expr, "Multiple adds to the same group",); - } + if !self.groups.insert(group_name.clone()) { + emit_error!(path, "Multiple adds to the same group",); + } + }, + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }) => { + if !self + .groups + .insert(syn::Ident::new(lit.value().as_str(), lit.span())) + { + emit_error!(lit, "Multiple adds to the same group",); + } + }, + expr => emit_error!(expr, "Can't parse expression"), } } } @@ -383,6 +388,10 @@ impl FieldSettings { emit_error!(&attr.meta, "Unknown attribute") } } + + if self.mandatory && self.groups.len() > 0 { + emit_error!(&meta.path, format!("Can't use both {MANDATORY} and {GROUP}"),); + } Ok(()) }) .unwrap_or_else(|err| emit_error!(&attr.meta, err)) diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 0fa2fc4..f0afe1d 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -4,7 +4,7 @@ use crate::symbol::{ ASSUME_MANDATORY, AT_LEAST, AT_MOST, BRUTE_FORCE, BUILDER, COMPILER, EXACT, GROUP, SINGLE, SOLVER, }; -use proc_macro_error::emit_error; +use proc_macro_error::{emit_error, emit_warning}; use quote::format_ident; use std::collections::{BTreeSet, HashMap}; use syn::Token; @@ -214,10 +214,18 @@ impl StructSettings { return; } }; + match attr.meta.require_list() { + Ok(list) => { + if list.tokens.is_empty() { + emit_warning!(list, "Empty atrribute list"); + } + } + Err(err) => emit_error!(attr, err), + }; match (&path_ident.to_string()).into() { GROUP => self.handle_group_attribute(attr), BUILDER => self.handle_builder_attribute(attr), - _ => emit_error!(&attr.meta, "Unknown attribute"), + _ => emit_error!(&attr, "Unknown attribute"), } } @@ -242,15 +250,6 @@ impl StructSettings { /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_builder_attribute(&mut self, attr: &syn::Attribute) { - match attr.meta.require_list() { - Ok(list) => { - if list.tokens.is_empty() { - return; - } - } - Err(err) => emit_error!(attr, err), - }; - attr.parse_nested_meta(|meta| { let path_ident = match meta.path.require_ident() { Ok(ident) => ident, @@ -264,12 +263,12 @@ impl StructSettings { ASSUME_MANDATORY => { if meta.input.peek(Token![=]) { let expr: syn::Expr = meta.value()?.parse()?; - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Bool(syn::LitBool { value, .. }), - .. - }) = expr - { - self.default_field_settings.mandatory = value; + match &expr { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Bool(syn::LitBool { value, .. }), + .. + }) => self.default_field_settings.mandatory = *value, + expr => emit_error!(expr, "Can't parse expression"), } } else { self.default_field_settings.mandatory = true; @@ -325,15 +324,6 @@ impl StructSettings { /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_group_attribute(&mut self, attr: &syn::Attribute) { - match attr.meta.require_list() { - Ok(list) => { - if list.tokens.is_empty() { - return; - } - } - Err(err) => emit_error!(attr, err), - }; - attr.parse_nested_meta(|meta| { let group_name = match meta.path.require_ident() { Ok(ident) => ident, diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_3.rs b/const_typed_builder_test/compile_fail/group_and_mandatory_3.rs new file mode 100644 index 0000000..ce8da33 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_3.rs @@ -0,0 +1,16 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = single)] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(mandatory)] + #[builder(group = quz)] + baz: Option, + qux: String, + } + + let _ = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr new file mode 100644 index 0000000..f93254c --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr @@ -0,0 +1,11 @@ +error: Can't use both mandatory and group + --> ./compile_fail/group_and_mandatory_3.rs:10:19 + | +10 | #[builder(group = quz)] + | ^^^^^ + +warning: Group can only be satisfied if all fields are initialized. Consider removing group and using [builder(mandatory)] instead + --> ./compile_fail/group_and_mandatory_3.rs:5:13 + | +5 | #[group(quz = single)] + | ^^^ diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_4.rs b/const_typed_builder_test/compile_fail/group_and_mandatory_4.rs new file mode 100644 index 0000000..8a87692 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_4.rs @@ -0,0 +1,16 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(quz = single)] + pub struct Foo { + #[builder(group = quz)] + bar: Option, + #[builder(group = quz)] + #[builder(mandatory)] + baz: Option, + qux: String, + } + + let _ = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr new file mode 100644 index 0000000..53b9116 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr @@ -0,0 +1,11 @@ +error: Can't use both mandatory and group + --> ./compile_fail/group_and_mandatory_4.rs:10:19 + | +10 | #[builder(mandatory)] + | ^^^^^^^^^ + +warning: Group can only be satisfied if all fields are initialized. Consider removing group and using [builder(mandatory)] instead + --> ./compile_fail/group_and_mandatory_4.rs:5:13 + | +5 | #[group(quz = single)] + | ^^^ diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index ba32b68..85d753f 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -369,19 +369,6 @@ mod test { .baz("Hello".to_string()) .build(); assert_eq!(expected, foo); - - // FIXME: Should fail - #[derive(Debug, Default, PartialEq, Eq, Builder)] - #[group(quz = single)] - pub struct Nope { - #[builder(group = quz)] - bar: Option, - #[builder(group = quz)] - #[builder(mandatory)] - baz: Option, - #[builder(mandatory)] - qux: Option, - } } #[test] From d29d1bd2c6aaa2dd4cf411d4143a4ff4e1c790d0 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:13:39 +0200 Subject: [PATCH 41/65] Linting stuff --- .../src/info/field_info.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index d595d8f..efd39d5 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -279,9 +279,12 @@ impl FieldSettings { /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) { match attr.path().require_ident() { - Ok(ident) if ident == BUILDER => {}, + Ok(ident) if ident == BUILDER => {} Ok(ident) => { - emit_error!(ident, format!("{ident} can't be used as a top level field attribute")); + emit_error!( + ident, + format!("{ident} can't be used as a top level field attribute") + ); } Err(err) => { emit_error!(attr.path(), err); @@ -365,7 +368,7 @@ impl FieldSettings { if !self.groups.insert(group_name.clone()) { emit_error!(path, "Multiple adds to the same group",); } - }, + } syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. @@ -376,7 +379,7 @@ impl FieldSettings { { emit_error!(lit, "Multiple adds to the same group",); } - }, + } expr => emit_error!(expr, "Can't parse expression"), } } @@ -389,8 +392,11 @@ impl FieldSettings { } } - if self.mandatory && self.groups.len() > 0 { - emit_error!(&meta.path, format!("Can't use both {MANDATORY} and {GROUP}"),); + if self.mandatory && !self.groups.is_empty() { + emit_error!( + &meta.path, + format!("Can't use both {MANDATORY} and {GROUP}"), + ); } Ok(()) }) From 9d1fdd4787d4996f5fd202edf148d4ed30592725 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 20:28:03 +0200 Subject: [PATCH 42/65] Run tests on stable compiler --- .../compile_fail/group_1.stderr | 13 +++++++++++++ .../compile_fail/group_and_mandatory_3.stderr | 17 ++++++++++++----- .../compile_fail/group_and_mandatory_4.stderr | 17 ++++++++++++----- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/const_typed_builder_test/compile_fail/group_1.stderr b/const_typed_builder_test/compile_fail/group_1.stderr index 637aa87..5df3acc 100644 --- a/const_typed_builder_test/compile_fail/group_1.stderr +++ b/const_typed_builder_test/compile_fail/group_1.stderr @@ -3,3 +3,16 @@ error: Group cannot be satisfied | 5 | #[group(baz = at_least(2))] | ^^^ + +error[E0599]: no function or associated item named `builder` found for struct `Foo` in the current scope + --> ./compile_fail/group_1.rs:10:27 + | +6 | pub struct Foo { + | -------------- function or associated item `builder` not found for this struct +... +10 | let foobuilder = Foo::builder().bar("Hello world!".to_string()); + | ^^^^^^^ function or associated item not found in `Foo` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `builder`, perhaps you need to implement it: + candidate #1: `const_typed_builder::Builder` diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr index f93254c..d032b37 100644 --- a/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr @@ -4,8 +4,15 @@ error: Can't use both mandatory and group 10 | #[builder(group = quz)] | ^^^^^ -warning: Group can only be satisfied if all fields are initialized. Consider removing group and using [builder(mandatory)] instead - --> ./compile_fail/group_and_mandatory_3.rs:5:13 - | -5 | #[group(quz = single)] - | ^^^ +error[E0599]: no function or associated item named `builder` found for struct `Foo` in the current scope + --> ./compile_fail/group_and_mandatory_3.rs:15:18 + | +6 | pub struct Foo { + | -------------- function or associated item `builder` not found for this struct +... +15 | let _ = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); + | ^^^^^^^ function or associated item not found in `Foo` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `builder`, perhaps you need to implement it: + candidate #1: `const_typed_builder::Builder` diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr index 53b9116..9ec83b4 100644 --- a/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr @@ -4,8 +4,15 @@ error: Can't use both mandatory and group 10 | #[builder(mandatory)] | ^^^^^^^^^ -warning: Group can only be satisfied if all fields are initialized. Consider removing group and using [builder(mandatory)] instead - --> ./compile_fail/group_and_mandatory_4.rs:5:13 - | -5 | #[group(quz = single)] - | ^^^ +error[E0599]: no function or associated item named `builder` found for struct `Foo` in the current scope + --> ./compile_fail/group_and_mandatory_4.rs:15:18 + | +6 | pub struct Foo { + | -------------- function or associated item `builder` not found for this struct +... +15 | let _ = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); + | ^^^^^^^ function or associated item not found in `Foo` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `builder`, perhaps you need to implement it: + candidate #1: `const_typed_builder::Builder` From 9f20b65983415b8cb1ecbf412bbb97859273d763 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:25:26 +0200 Subject: [PATCH 43/65] Improve can_be_valid --- .../src/generator/group_generator.rs | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index e158470..dac5ad8 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -31,26 +31,43 @@ impl<'a> GroupGenerator<'a> { fn can_be_valid(groups: &[&'a GroupInfo]) { groups.iter().for_each(|group| { - let associated_count = group.indices().len(); + let valid_range = 1..group.indices().len(); + if valid_range.is_empty() { + emit_warning!(group.name(), format!("There is not an valid expected count")) + } else if !valid_range.contains(&group.expected_count()) { + emit_warning!(group.name(), format!("Expected count is outside of valid range {valid_range:#?}")); + } match group.group_type() { - crate::info::GroupType::Exact(expected) => { - match associated_count.cmp(expected) { + crate::info::GroupType::Exact(expected) => { + match expected.cmp(&valid_range.start) { std::cmp::Ordering::Less => emit_error!(group.name(), "Group can never be satisfied"), - std::cmp::Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized. Consider removing group and using [builder(mandatory)] instead"), - std::cmp::Ordering::Greater => {}, + std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, + } + match expected.cmp(&valid_range.end) { + std::cmp::Ordering::Less => {} + std::cmp::Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), + std::cmp::Ordering::Greater => emit_error!(group.name(), format!("Group can never be satisfied. Need exact {} out of {} fields", expected, valid_range.end)), } }, crate::info::GroupType::AtLeast(expected) => { - if associated_count < *expected { - emit_error!(group.name(), "Group cannot be satisfied"); - } - if *expected == 0 { - emit_warning!(group.name(), "Group has no effect. Consider removing the group") + match expected.cmp(&valid_range.start) { + std::cmp::Ordering::Less => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), + std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, + } + match expected.cmp(&valid_range.end) { + std::cmp::Ordering::Less => {} + std::cmp::Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), + std::cmp::Ordering::Greater => emit_error!(group.name(), format!("Group can never be satisfied. Need at least {} out of {} fields", expected, valid_range.end)), } }, crate::info::GroupType::AtMost(expected) => { - if *expected == 0 { - emit_warning!(group.name(), "Group can only be satisfied if none of the fields are initialized. Consider removing group and using [builder(skip)] instead"); + match expected.cmp(&valid_range.start) { + std::cmp::Ordering::Less => emit_error!(group.name(), "This group prevents all of the fields to be initialized"; hint = "Removing the group and use [builder(skip)] instead"), + std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, + } + match expected.cmp(&valid_range.end) { + std::cmp::Ordering::Less => {} + std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), } }, } From 81b18de4ac8525290127bf422989888fc2138a62 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:25:44 +0200 Subject: [PATCH 44/65] Fix tests --- README.md | 11 ++++------- const_typed_builder_test/compile_fail/group_1.stderr | 2 +- const_typed_builder_test/src/lib.rs | 6 ++++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ec8c17b..d0ae2f3 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,7 @@ pub struct ResourceLimits { // ... } #[derive(Debug, Builder)] -#[group(program = at_least(1), deprecated = at_most(0))] -// ^ `my_group = at_most(0)` can be used to denote deprecated -// fields that you still want to deserialize. It will be replaced by the attribute -// `#[builder(skip)]` on a field in a future version +#[group(program = at_least(1))] pub struct Launchd { #[builder(mandatory)] label: Option, @@ -63,9 +60,9 @@ pub struct Launchd { #[builder(group = program)] program_arguments: Option>, // ... - #[builder(group = deprecated)] + #[builder(skip)] on_demand: Option, // NB: deprecated (see KeepAlive), but still needed for reading old plists. - #[builder(group = deprecated)] + #[builder(skip)] service_ipc: Option, // NB: "Please remove this key from your launchd.plist." // ... #[builder(propagate)] @@ -81,7 +78,7 @@ let launchd = Launchd::builder() .program_arguments( // <- 2: .. We can remove either one, but never both vec!["my_arg".to_string()] ) -// .on_demand(false) <- 3: If this is uncommented then the struct will never be valid +// .on_demand(false) <- 3: This function doesn't exist .soft_resource_limits(|builder| Some(builder.core(Some(1)).build()) // <- 4: Propagating to `ResourceLimits::builder` ) diff --git a/const_typed_builder_test/compile_fail/group_1.stderr b/const_typed_builder_test/compile_fail/group_1.stderr index 5df3acc..59e87ec 100644 --- a/const_typed_builder_test/compile_fail/group_1.stderr +++ b/const_typed_builder_test/compile_fail/group_1.stderr @@ -1,4 +1,4 @@ -error: Group cannot be satisfied +error: Group can never be satisfied. Need at least 2 out of 1 fields --> ./compile_fail/group_1.rs:5:13 | 5 | #[group(baz = at_least(2))] diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index 85d753f..4d33e8c 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -244,10 +244,13 @@ mod test { pub struct Foo { #[builder(group = baz)] bar: Option, + #[builder(group = baz)] + baz: Option, } let expected = Foo { bar: Some("Hello world!".to_string()), + baz: None, }; let foo = Foo::builder().bar("Hello world!".to_string()).build(); assert_eq!(expected, foo); @@ -261,10 +264,13 @@ mod test { pub struct Foo { #[builder(group = baz)] bar: Option, + #[builder(group = baz)] + baz: Option, } let expected = Foo { bar: Some("Hello world!".to_string()), + baz: None, }; let foo = Foo::builder().bar("Hello world!".to_string()).build(); assert_eq!(expected, foo); From abd0918b847e32d05aca19906b065aeb53b8191c Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:28:23 +0200 Subject: [PATCH 45/65] run fmt --- .../src/generator/group_generator.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index dac5ad8..a4fe45b 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -38,11 +38,11 @@ impl<'a> GroupGenerator<'a> { emit_warning!(group.name(), format!("Expected count is outside of valid range {valid_range:#?}")); } match group.group_type() { - crate::info::GroupType::Exact(expected) => { + crate::info::GroupType::Exact(expected) => { match expected.cmp(&valid_range.start) { std::cmp::Ordering::Less => emit_error!(group.name(), "Group can never be satisfied"), std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, - } + } match expected.cmp(&valid_range.end) { std::cmp::Ordering::Less => {} std::cmp::Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), @@ -53,7 +53,7 @@ impl<'a> GroupGenerator<'a> { match expected.cmp(&valid_range.start) { std::cmp::Ordering::Less => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, - } + } match expected.cmp(&valid_range.end) { std::cmp::Ordering::Less => {} std::cmp::Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), @@ -64,7 +64,7 @@ impl<'a> GroupGenerator<'a> { match expected.cmp(&valid_range.start) { std::cmp::Ordering::Less => emit_error!(group.name(), "This group prevents all of the fields to be initialized"; hint = "Removing the group and use [builder(skip)] instead"), std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, - } + } match expected.cmp(&valid_range.end) { std::cmp::Ordering::Less => {} std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), From e7c0b3872370e425ddabe577f3750ba5b5576738 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:31:19 +0200 Subject: [PATCH 46/65] a word --- const_typed_builder_derive/src/generator/group_generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index a4fe45b..7bc586f 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -62,7 +62,7 @@ impl<'a> GroupGenerator<'a> { }, crate::info::GroupType::AtMost(expected) => { match expected.cmp(&valid_range.start) { - std::cmp::Ordering::Less => emit_error!(group.name(), "This group prevents all of the fields to be initialized"; hint = "Removing the group and use [builder(skip)] instead"), + std::cmp::Ordering::Less => emit_error!(group.name(), "This group prevents all of the fields to be initialized"; hint = "Remove the group and use [builder(skip)] instead"), std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, } match expected.cmp(&valid_range.end) { From 9405dc0c4c3395841208ada8cc524c6b80214b89 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:39:39 +0200 Subject: [PATCH 47/65] replace path with use --- .../src/generator/group_generator.rs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index 7bc586f..39a3c78 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -6,7 +6,7 @@ use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; use proc_macro_error::{emit_error, emit_warning}; use quote::{format_ident, quote}; -use std::collections::BTreeSet; +use std::{collections::BTreeSet, cmp::Ordering}; /// The `GroupGenerator` struct is responsible for generating code related to groups within the builder, including correctness checks and verifications. #[derive(Debug)] @@ -38,36 +38,36 @@ impl<'a> GroupGenerator<'a> { emit_warning!(group.name(), format!("Expected count is outside of valid range {valid_range:#?}")); } match group.group_type() { - crate::info::GroupType::Exact(expected) => { + GroupType::Exact(expected) => { match expected.cmp(&valid_range.start) { - std::cmp::Ordering::Less => emit_error!(group.name(), "Group can never be satisfied"), - std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, + Ordering::Less => emit_error!(group.name(), "Group can never be satisfied"), + Ordering::Equal | Ordering::Greater => {}, } match expected.cmp(&valid_range.end) { - std::cmp::Ordering::Less => {} - std::cmp::Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), - std::cmp::Ordering::Greater => emit_error!(group.name(), format!("Group can never be satisfied. Need exact {} out of {} fields", expected, valid_range.end)), + Ordering::Less => {} + Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), + Ordering::Greater => emit_error!(group.name(), format!("Group can never be satisfied. Need exact {} out of {} fields", expected, valid_range.end)), } }, - crate::info::GroupType::AtLeast(expected) => { + GroupType::AtLeast(expected) => { match expected.cmp(&valid_range.start) { - std::cmp::Ordering::Less => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), - std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, + Ordering::Less => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), + Ordering::Equal | Ordering::Greater => {}, } match expected.cmp(&valid_range.end) { - std::cmp::Ordering::Less => {} - std::cmp::Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), - std::cmp::Ordering::Greater => emit_error!(group.name(), format!("Group can never be satisfied. Need at least {} out of {} fields", expected, valid_range.end)), + Ordering::Less => {} + Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), + Ordering::Greater => emit_error!(group.name(), format!("Group can never be satisfied. Need at least {} out of {} fields", expected, valid_range.end)), } }, - crate::info::GroupType::AtMost(expected) => { + GroupType::AtMost(expected) => { match expected.cmp(&valid_range.start) { - std::cmp::Ordering::Less => emit_error!(group.name(), "This group prevents all of the fields to be initialized"; hint = "Remove the group and use [builder(skip)] instead"), - std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {}, + Ordering::Less => emit_error!(group.name(), "This group prevents all of the fields to be initialized"; hint = "Remove the group and use [builder(skip)] instead"), + Ordering::Equal | Ordering::Greater => {}, } match expected.cmp(&valid_range.end) { - std::cmp::Ordering::Less => {} - std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), + Ordering::Less => {} + Ordering::Equal | Ordering::Greater => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), } }, } From d2d6add56238e4baa20c9b152bc2abd4dcf446b6 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:40:14 +0200 Subject: [PATCH 48/65] fmt --- const_typed_builder_derive/src/generator/group_generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index 39a3c78..7af83fe 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -6,7 +6,7 @@ use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; use proc_macro_error::{emit_error, emit_warning}; use quote::{format_ident, quote}; -use std::{collections::BTreeSet, cmp::Ordering}; +use std::{cmp::Ordering, collections::BTreeSet}; /// The `GroupGenerator` struct is responsible for generating code related to groups within the builder, including correctness checks and verifications. #[derive(Debug)] From 241e3ba0be22b504af0f2bae7ce7e0957cf1c491 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 23:42:41 +0200 Subject: [PATCH 49/65] Move grouyp check to group_info. Improve error messages --- .../src/generator/group_generator.rs | 50 +----------- .../src/info/group_info.rs | 79 ++++++++++++++++++- 2 files changed, 80 insertions(+), 49 deletions(-) diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index 7af83fe..12fc24e 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -4,9 +4,8 @@ use crate::{ }; use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; -use proc_macro_error::{emit_error, emit_warning}; use quote::{format_ident, quote}; -use std::{cmp::Ordering, collections::BTreeSet}; +use std::collections::BTreeSet; /// The `GroupGenerator` struct is responsible for generating code related to groups within the builder, including correctness checks and verifications. #[derive(Debug)] @@ -25,55 +24,10 @@ impl<'a> GroupGenerator<'a> { /// /// A `GroupGenerator` instance initialized with the provided groups. pub fn new(groups: Vec<&'a GroupInfo>) -> Self { - Self::can_be_valid(&groups); + groups.iter().for_each(|group| group.check()); Self { groups } } - fn can_be_valid(groups: &[&'a GroupInfo]) { - groups.iter().for_each(|group| { - let valid_range = 1..group.indices().len(); - if valid_range.is_empty() { - emit_warning!(group.name(), format!("There is not an valid expected count")) - } else if !valid_range.contains(&group.expected_count()) { - emit_warning!(group.name(), format!("Expected count is outside of valid range {valid_range:#?}")); - } - match group.group_type() { - GroupType::Exact(expected) => { - match expected.cmp(&valid_range.start) { - Ordering::Less => emit_error!(group.name(), "Group can never be satisfied"), - Ordering::Equal | Ordering::Greater => {}, - } - match expected.cmp(&valid_range.end) { - Ordering::Less => {} - Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), - Ordering::Greater => emit_error!(group.name(), format!("Group can never be satisfied. Need exact {} out of {} fields", expected, valid_range.end)), - } - }, - GroupType::AtLeast(expected) => { - match expected.cmp(&valid_range.start) { - Ordering::Less => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), - Ordering::Equal | Ordering::Greater => {}, - } - match expected.cmp(&valid_range.end) { - Ordering::Less => {} - Ordering::Equal => emit_warning!(group.name(), "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead"), - Ordering::Greater => emit_error!(group.name(), format!("Group can never be satisfied. Need at least {} out of {} fields", expected, valid_range.end)), - } - }, - GroupType::AtMost(expected) => { - match expected.cmp(&valid_range.start) { - Ordering::Less => emit_error!(group.name(), "This group prevents all of the fields to be initialized"; hint = "Remove the group and use [builder(skip)] instead"), - Ordering::Equal | Ordering::Greater => {}, - } - match expected.cmp(&valid_range.end) { - Ordering::Less => {} - Ordering::Equal | Ordering::Greater => emit_warning!(group.name(), "Group has no effect"; hint = "Consider removing the group"), - } - }, - } - }); - } - /// Returns all valid combinations of the const generics for the grouped fields /// /// # Returns diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group_info.rs index b3e55cf..0ec628c 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group_info.rs @@ -1,5 +1,7 @@ +use proc_macro_error::{emit_warning, emit_error}; + use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; -use std::{collections::BTreeSet, hash::Hash}; +use std::{collections::BTreeSet, hash::Hash, cmp::Ordering}; /// Represents information about a group, including its name, member count, and group type. #[derive(Debug, Clone)] @@ -75,6 +77,81 @@ impl GroupInfo { GroupType::AtMost(count) => applicable_indices_count <= count, } } + + pub fn check(&self) { + let valid_range = 1..self.indices().len(); + if valid_range.is_empty() { + emit_warning!(self.name, format!("There is not an valid expected count")) + } else if !valid_range.contains(&self.expected_count()) { + emit_warning!(self.name, format!("Expected count is outside of valid range {valid_range:#?}")); + } + match self.group_type() { + GroupType::Exact(expected) => { + match expected.cmp(&valid_range.start) { + Ordering::Less => emit_error!( + self.name, + "This group prevents all of the fields to be initialized"; + hint = "Remove the group and use [builder(skip)] instead" + ), + Ordering::Equal | Ordering::Greater => {}, + } + match expected.cmp(&valid_range.end) { + Ordering::Less => {} + Ordering::Equal => emit_warning!( + self.name, + "Group can only be satisfied if all fields are initialized"; + hint = "Consider removing group and using [builder(mandatory)] instead" + ), + Ordering::Greater => emit_error!( + self.name, + "Group can never be satisfied"; + note = format!("Expected amount of fields: exact {}, amount of available fields: {}", expected, valid_range.end)), + } + }, + GroupType::AtLeast(expected) => { + match expected.cmp(&valid_range.start) { + Ordering::Less => emit_warning!( + self.name, + "Group has no effect"; + hint = "Consider removing the group" + ), + Ordering::Equal | Ordering::Greater => {}, + } + match expected.cmp(&valid_range.end) { + Ordering::Less => {} + Ordering::Equal => emit_warning!( + self.name, + "Group can only be satisfied if all fields are initialized"; + hint = "Consider removing group and using [builder(mandatory)] instead" + ), + Ordering::Greater => emit_error!( + self.name, + "Group can never be satisfied"; + note = format!("Expected amount of fields: at least {}, amount of available fields: {}", expected, valid_range.end); + ), + } + }, + GroupType::AtMost(expected) => { + match expected.cmp(&valid_range.start) { + Ordering::Less => emit_error!( + self.name, + "This group prevents all of the fields to be initialized"; + hint = "Remove the group and use [builder(skip)] instead"; + note = format!("Expected amount of fields: at most {}, amount of available fields: {}", expected, valid_range.start) + ), + Ordering::Equal | Ordering::Greater => {}, + } + match expected.cmp(&valid_range.end) { + Ordering::Less => {} + Ordering::Equal | Ordering::Greater => emit_warning!( + self.name, + "Group has no effect"; + hint = "Consider removing the group" + ), + } + }, + } + } } impl Eq for GroupInfo {} From eea25420df9e307a7a22ce95adb0a7ec5abf954a Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 2 Oct 2023 23:43:23 +0200 Subject: [PATCH 50/65] Improve error messages and update tests --- .../src/info/field_info.rs | 44 +++++++++++-------- .../src/info/struct_info.rs | 34 +++++++++++--- .../compile_fail/group_1.stderr | 5 ++- .../compile_fail/group_3.rs | 14 ++++++ .../compile_fail/group_3.stderr | 21 +++++++++ .../compile_fail/group_and_mandatory_3.rs | 3 +- .../compile_fail/group_and_mandatory_3.stderr | 17 ++++--- .../compile_fail/group_and_mandatory_4.stderr | 5 ++- 8 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 const_typed_builder_test/compile_fail/group_3.rs create mode 100644 const_typed_builder_test/compile_fail/group_3.stderr diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index efd39d5..9e07911 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -88,7 +88,11 @@ impl<'a> FieldInfo<'a> { { group.associate(index); } else { - emit_error!(group_name, "Can't find group"); + emit_error!( + group_name, + format!("No group called {group_name} is available"); + hint = format!("You might want to add a #[{GROUP}(...)] attribute to the container") + ); } } @@ -111,7 +115,7 @@ impl<'a> FieldInfo<'a> { Some(info) } else { - emit_error!(field, "Unnamed fields are not supported",); + emit_error!(field, "Unnamed fields are not supported"); None } } @@ -283,7 +287,7 @@ impl FieldSettings { Ok(ident) => { emit_error!( ident, - format!("{ident} can't be used as a top level field attribute") + format!("{ident} can't be used as a field attribute") ); } Err(err) => { @@ -355,32 +359,35 @@ impl FieldSettings { GROUP => { if meta.input.peek(Token![=]) { let expr: syn::Expr = meta.value()?.parse()?; - match expr { + let group_name = match expr { syn::Expr::Path(ExprPath { path, .. }) => { - let group_name = match path.require_ident() { + match path.require_ident() { Ok(ident) => ident, Err(err) => { emit_error!(path, err); return Ok(()); } - }; - - if !self.groups.insert(group_name.clone()) { - emit_error!(path, "Multiple adds to the same group",); - } + }.clone() } syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. }) => { - if !self - .groups - .insert(syn::Ident::new(lit.value().as_str(), lit.span())) - { - emit_error!(lit, "Multiple adds to the same group",); - } + syn::Ident::new(lit.value().as_str(), lit.span()) } - expr => emit_error!(expr, "Can't parse expression"), + expr => { + emit_error!(expr, "Can't parse expression"); + return Ok(()); + }, + }; + if self.groups.contains(&group_name) { + emit_error!( + group_name.span(), + "Multiple adds to the same group"; + help = self.groups.get(&group_name).unwrap().span() => "Remove this attribute" + ); + } else { + self.groups.insert(group_name); } } } @@ -395,7 +402,8 @@ impl FieldSettings { if self.mandatory && !self.groups.is_empty() { emit_error!( &meta.path, - format!("Can't use both {MANDATORY} and {GROUP}"), + format!("Can't use both {MANDATORY} and {GROUP} attributes"); + hint = "Remove either types of attribute from this field" ); } Ok(()) diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index f0afe1d..a0898fc 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -344,7 +344,10 @@ impl StructSettings { } }, _ => { - emit_error!(&attr.meta, "Can't find group type"); + emit_error!( + &attr.meta, "No group type specified"; + hint = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST + ); return Ok(()); } }; @@ -359,11 +362,19 @@ impl StructSettings { AT_LEAST => GroupType::AtLeast(group_args), AT_MOST => GroupType::AtMost(group_args), SINGLE => { - emit_error!(args, "`single` doesn't take any arguments",); + emit_error!( + args, + "`{}` doesn't take any arguments", SINGLE; + help = "`{}` is shorthand for {}(1)", SINGLE, EXACT + ); return Ok(()); } _ => { - emit_error!(group_type, "Unknown group type"); + emit_error!( + group_type, + "Unknown group type"; + help = "Known group types are {}, {} and {}", EXACT, AT_LEAST, AT_MOST + ); return Ok(()); } }, @@ -389,18 +400,29 @@ impl StructSettings { }; match (&group_type.to_string()).into() { EXACT | AT_LEAST | AT_MOST => { - emit_error!(&attr.meta, "Missing arguments for group type"); + emit_error!( + &attr.meta, + "Missing arguments for group type"; + help = "Try `{}(1)`, or any other usize", &group_type + ); return Ok(()); } SINGLE => GroupType::Exact(1), _ => { - emit_error!(&attr.meta, "Can't parse group"); + emit_error!( + group_type, + "Unknown group type"; + help = "Known group types are {}, {} and {}", EXACT, AT_LEAST, AT_MOST + ); return Ok(()); } } } _ => { - emit_error!(&attr.meta, "Can't find group type"); + emit_error!( + &attr.meta, "No group type specified"; + hint = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST + ); return Ok(()); } }; diff --git a/const_typed_builder_test/compile_fail/group_1.stderr b/const_typed_builder_test/compile_fail/group_1.stderr index 59e87ec..0ff3bf9 100644 --- a/const_typed_builder_test/compile_fail/group_1.stderr +++ b/const_typed_builder_test/compile_fail/group_1.stderr @@ -1,4 +1,7 @@ -error: Group can never be satisfied. Need at least 2 out of 1 fields +error: Group can never be satisfied + + = note: Expected amount of fields: at least 2, amount of available fields: 1 + --> ./compile_fail/group_1.rs:5:13 | 5 | #[group(baz = at_least(2))] diff --git a/const_typed_builder_test/compile_fail/group_3.rs b/const_typed_builder_test/compile_fail/group_3.rs new file mode 100644 index 0000000..2a90345 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_3.rs @@ -0,0 +1,14 @@ +use const_typed_builder::Builder; + +fn main() { + #[derive(Debug, Default, PartialEq, Eq, Builder)] + #[group(baz = at_least(2))] + pub struct Foo { + #[builder(group = baz)] + #[builder(group = baz)] + bar: Option, + #[builder(group = baz)] + baz: Option, + } + let foobuilder = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!".to_string()).build(); +} \ No newline at end of file diff --git a/const_typed_builder_test/compile_fail/group_3.stderr b/const_typed_builder_test/compile_fail/group_3.stderr new file mode 100644 index 0000000..0a11742 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_3.stderr @@ -0,0 +1,21 @@ +error: Multiple adds to the same group + + = help: Remove this attribute + + --> ./compile_fail/group_3.rs:8:27 + | +8 | #[builder(group = baz)] + | ^^^ + +error[E0599]: no function or associated item named `builder` found for struct `Foo` in the current scope + --> ./compile_fail/group_3.rs:13:27 + | +6 | pub struct Foo { + | -------------- function or associated item `builder` not found for this struct +... +13 | let foobuilder = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!".to_string()).build(); + | ^^^^^^^ function or associated item not found in `Foo` + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `builder`, perhaps you need to implement it: + candidate #1: `const_typed_builder::Builder` diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_3.rs b/const_typed_builder_test/compile_fail/group_and_mandatory_3.rs index ce8da33..e7fd1d5 100644 --- a/const_typed_builder_test/compile_fail/group_and_mandatory_3.rs +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_3.rs @@ -6,8 +6,7 @@ fn main() { pub struct Foo { #[builder(group = quz)] bar: Option, - #[builder(mandatory)] - #[builder(group = quz)] + #[builder(mandatory, group = quz)] baz: Option, qux: String, } diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr index d032b37..6f7697c 100644 --- a/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr @@ -1,16 +1,19 @@ -error: Can't use both mandatory and group - --> ./compile_fail/group_and_mandatory_3.rs:10:19 - | -10 | #[builder(group = quz)] - | ^^^^^ +error: Can't use both mandatory and group attributes + + = help: Remove either types of attribute from this field + + --> ./compile_fail/group_and_mandatory_3.rs:9:30 + | +9 | #[builder(mandatory, group = quz)] + | ^^^^^ error[E0599]: no function or associated item named `builder` found for struct `Foo` in the current scope - --> ./compile_fail/group_and_mandatory_3.rs:15:18 + --> ./compile_fail/group_and_mandatory_3.rs:14:18 | 6 | pub struct Foo { | -------------- function or associated item `builder` not found for this struct ... -15 | let _ = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); +14 | let _ = Foo::builder().bar("Hello".to_string()).baz("Hello".to_string()).qux("world!".to_string()).build(); | ^^^^^^^ function or associated item not found in `Foo` | = help: items from traits can only be used if the trait is implemented and in scope diff --git a/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr index 9ec83b4..c708d76 100644 --- a/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr @@ -1,4 +1,7 @@ -error: Can't use both mandatory and group +error: Can't use both mandatory and group attributes + + = help: Remove either types of attribute from this field + --> ./compile_fail/group_and_mandatory_4.rs:10:19 | 10 | #[builder(mandatory)] From 9f154195531ad2d64add106aa65e65c7613a5854 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 00:33:20 +0200 Subject: [PATCH 51/65] Improve error messages --- .../src/info/field_info.rs | 41 +++++++++---- .../src/info/struct_info.rs | 61 ++++++++++++++----- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index 9e07911..d8f2f4c 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -282,18 +282,23 @@ impl FieldSettings { /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) { - match attr.path().require_ident() { - Ok(ident) if ident == BUILDER => {} + let attr_ident = match attr.path().require_ident() { + Ok(ident) if ident == BUILDER => {ident} Ok(ident) => { emit_error!( ident, format!("{ident} can't be used as a field attribute") ); + return; } Err(err) => { - emit_error!(attr.path(), err); + emit_error!( + attr.path(), "Can't parse attribute"; + note = err + ); + return; } - } + }; match attr.meta.require_list() { Ok(list) => { @@ -301,14 +306,22 @@ impl FieldSettings { emit_warning!(list, "Empty atrribute list"); } } - Err(err) => emit_error!(attr, err), + Err(err) => emit_error!( + attr, "Attribute expected contain a list of specifiers"; + help = "Try specifying it like #[{}(specifier)]", attr_ident; + note = err + ), } attr.parse_nested_meta(|meta| { let path_ident = match meta.path.require_ident() { Ok(ident) => ident, Err(err) => { - emit_error!(&attr.meta, err); + emit_error!( + &attr.meta, "Specifier cannot be parsed"; + help = "Try specifying it like #[{}(specifier)]", attr_ident; + note = err + ); return Ok(()); } }; @@ -364,7 +377,11 @@ impl FieldSettings { match path.require_ident() { Ok(ident) => ident, Err(err) => { - emit_error!(path, err); + emit_error!( + path, "Group name not specified correctly"; + help = "Try defining it like #[{}(foo)]", BUILDER; + note = err + ); return Ok(()); } }.clone() @@ -376,14 +393,13 @@ impl FieldSettings { syn::Ident::new(lit.value().as_str(), lit.span()) } expr => { - emit_error!(expr, "Can't parse expression"); + emit_error!(expr, "Can't parse group name"); return Ok(()); }, }; if self.groups.contains(&group_name) { emit_error!( - group_name.span(), - "Multiple adds to the same group"; + group_name.span(), "Multiple adds to the same group"; help = self.groups.get(&group_name).unwrap().span() => "Remove this attribute" ); } else { @@ -408,7 +424,10 @@ impl FieldSettings { } Ok(()) }) - .unwrap_or_else(|err| emit_error!(&attr.meta, err)) + .unwrap_or_else(|err| emit_error!( + &attr.meta, "Unknown error"; + note = err + )) } } diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index a0898fc..32bc446 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -207,10 +207,13 @@ impl StructSettings { /// /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) { - let path_ident = match attr.path().require_ident() { + let attr_ident = match attr.path().require_ident() { Ok(ident) => ident, Err(err) => { - emit_error!(attr.path(), err); + emit_error!( + attr.path(), "Can't parse attribute"; + note = err + ); return; } }; @@ -220,9 +223,13 @@ impl StructSettings { emit_warning!(list, "Empty atrribute list"); } } - Err(err) => emit_error!(attr, err), - }; - match (&path_ident.to_string()).into() { + Err(err) => emit_error!( + attr, "Attribute expected contain a list of specifiers"; + help = "Try specifying it like #[{}(specifier)]", attr_ident; + note = err + ), + } + match (&attr_ident.to_string()).into() { GROUP => self.handle_group_attribute(attr), BUILDER => self.handle_builder_attribute(attr), _ => emit_error!(&attr, "Unknown attribute"), @@ -254,7 +261,11 @@ impl StructSettings { let path_ident = match meta.path.require_ident() { Ok(ident) => ident, Err(err) => { - emit_error!(&attr.meta, err); + emit_error!( + &attr.meta, "Specifier cannot be parsed"; + help = "Try specifying it like #[{}(specifier)]", BUILDER; + note = err + ); return Ok(()); } }; @@ -300,7 +311,10 @@ impl StructSettings { } Ok(()) }) - .unwrap_or_else(|err| emit_error!(&attr.meta, err)) + .unwrap_or_else(|err| emit_error!( + &attr.meta, "Unknown error"; + note = err + )) } /// Handles the parsing and processing of group attributes applied to a struct. @@ -328,7 +342,11 @@ impl StructSettings { let group_name = match meta.path.require_ident() { Ok(ident) => ident, Err(err) => { - emit_error!(&attr.meta, err); + emit_error!( + &meta.path , "Group name is not specified correctly"; + help = "Try to define it like `#[{}(foo = {}(1))]`", GROUP, AT_LEAST; + note = err + ); return Ok(()); } }; @@ -339,14 +357,18 @@ impl StructSettings { syn::Expr::Path(syn::ExprPath { path, .. }) => match path.require_ident() { Ok(ident) => ident, Err(err) => { - emit_error!(&attr.meta, err); + emit_error!( + &meta.path , "Group type is not specified correctly"; + help = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST; + note = err + ); return Ok(()); } }, _ => { emit_error!( &attr.meta, "No group type specified"; - hint = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST + help = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST ); return Ok(()); } @@ -371,15 +393,17 @@ impl StructSettings { } _ => { emit_error!( - group_type, - "Unknown group type"; + group_type, "Unknown group type"; help = "Known group types are {}, {} and {}", EXACT, AT_LEAST, AT_MOST ); return Ok(()); } }, Err(err) => { - emit_error!(val, err); + emit_error!( + val, "Couldn't parse group argument"; + note = err + ); return Ok(()); } }, @@ -394,7 +418,11 @@ impl StructSettings { let group_type = match path.require_ident() { Ok(ident) => ident, Err(err) => { - emit_error!(path, err); + emit_error!( + &meta.path , "Group type is not specified correctly"; + help = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST; + note = err + ); return Ok(()); } }; @@ -433,6 +461,9 @@ impl StructSettings { ); Ok(()) }) - .unwrap_or_else(|err| emit_error!(&attr.meta, err)) + .unwrap_or_else(|err| emit_error!( + &attr.meta, "Unknown error"; + note = err + )) } } From 8662cc0eeab1007fef15875a1ec39e4e3d5ec614 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 00:35:30 +0200 Subject: [PATCH 52/65] fmt --- .../src/info/field_info.rs | 9 ++-- .../src/info/group_info.rs | 51 ++++++++++--------- .../src/info/struct_info.rs | 16 +++--- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index d8f2f4c..5e8b5fe 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -89,7 +89,7 @@ impl<'a> FieldInfo<'a> { group.associate(index); } else { emit_error!( - group_name, + group_name, format!("No group called {group_name} is available"); hint = format!("You might want to add a #[{GROUP}(...)] attribute to the container") ); @@ -283,12 +283,9 @@ impl FieldSettings { /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) { let attr_ident = match attr.path().require_ident() { - Ok(ident) if ident == BUILDER => {ident} + Ok(ident) if ident == BUILDER => ident, Ok(ident) => { - emit_error!( - ident, - format!("{ident} can't be used as a field attribute") - ); + emit_error!(ident, format!("{ident} can't be used as a field attribute")); return; } Err(err) => { diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group_info.rs index 0ec628c..6447190 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group_info.rs @@ -1,7 +1,7 @@ -use proc_macro_error::{emit_warning, emit_error}; +use proc_macro_error::{emit_error, emit_warning}; use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; -use std::{collections::BTreeSet, hash::Hash, cmp::Ordering}; +use std::{cmp::Ordering, collections::BTreeSet, hash::Hash}; /// Represents information about a group, including its name, member count, and group type. #[derive(Debug, Clone)] @@ -83,73 +83,76 @@ impl GroupInfo { if valid_range.is_empty() { emit_warning!(self.name, format!("There is not an valid expected count")) } else if !valid_range.contains(&self.expected_count()) { - emit_warning!(self.name, format!("Expected count is outside of valid range {valid_range:#?}")); + emit_warning!( + self.name, + format!("Expected count is outside of valid range {valid_range:#?}") + ); } match self.group_type() { GroupType::Exact(expected) => { match expected.cmp(&valid_range.start) { Ordering::Less => emit_error!( - self.name, - "This group prevents all of the fields to be initialized"; + self.name, + "This group prevents all of the fields to be initialized"; hint = "Remove the group and use [builder(skip)] instead" ), - Ordering::Equal | Ordering::Greater => {}, + Ordering::Equal | Ordering::Greater => {} } match expected.cmp(&valid_range.end) { Ordering::Less => {} Ordering::Equal => emit_warning!( - self.name, - "Group can only be satisfied if all fields are initialized"; + self.name, + "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead" ), Ordering::Greater => emit_error!( - self.name, + self.name, "Group can never be satisfied"; note = format!("Expected amount of fields: exact {}, amount of available fields: {}", expected, valid_range.end)), } - }, + } GroupType::AtLeast(expected) => { match expected.cmp(&valid_range.start) { Ordering::Less => emit_warning!( - self.name, - "Group has no effect"; + self.name, + "Group has no effect"; hint = "Consider removing the group" ), - Ordering::Equal | Ordering::Greater => {}, + Ordering::Equal | Ordering::Greater => {} } match expected.cmp(&valid_range.end) { Ordering::Less => {} Ordering::Equal => emit_warning!( - self.name, - "Group can only be satisfied if all fields are initialized"; + self.name, + "Group can only be satisfied if all fields are initialized"; hint = "Consider removing group and using [builder(mandatory)] instead" ), Ordering::Greater => emit_error!( - self.name, + self.name, "Group can never be satisfied"; note = format!("Expected amount of fields: at least {}, amount of available fields: {}", expected, valid_range.end); ), } - }, + } GroupType::AtMost(expected) => { match expected.cmp(&valid_range.start) { Ordering::Less => emit_error!( - self.name, - "This group prevents all of the fields to be initialized"; - hint = "Remove the group and use [builder(skip)] instead"; + self.name, + "This group prevents all of the fields to be initialized"; + hint = "Remove the group and use [builder(skip)] instead"; note = format!("Expected amount of fields: at most {}, amount of available fields: {}", expected, valid_range.start) ), - Ordering::Equal | Ordering::Greater => {}, + Ordering::Equal | Ordering::Greater => {} } match expected.cmp(&valid_range.end) { Ordering::Less => {} Ordering::Equal | Ordering::Greater => emit_warning!( - self.name, - "Group has no effect"; + self.name, + "Group has no effect"; hint = "Consider removing the group" ), } - }, + } } } } diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 32bc446..35a1492 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -311,10 +311,12 @@ impl StructSettings { } Ok(()) }) - .unwrap_or_else(|err| emit_error!( - &attr.meta, "Unknown error"; - note = err - )) + .unwrap_or_else(|err| { + emit_error!( + &attr.meta, "Unknown error"; + note = err + ) + }) } /// Handles the parsing and processing of group attributes applied to a struct. @@ -385,7 +387,7 @@ impl StructSettings { AT_MOST => GroupType::AtMost(group_args), SINGLE => { emit_error!( - args, + args, "`{}` doesn't take any arguments", SINGLE; help = "`{}` is shorthand for {}(1)", SINGLE, EXACT ); @@ -429,7 +431,7 @@ impl StructSettings { match (&group_type.to_string()).into() { EXACT | AT_LEAST | AT_MOST => { emit_error!( - &attr.meta, + &attr.meta, "Missing arguments for group type"; help = "Try `{}(1)`, or any other usize", &group_type ); @@ -438,7 +440,7 @@ impl StructSettings { SINGLE => GroupType::Exact(1), _ => { emit_error!( - group_type, + group_type, "Unknown group type"; help = "Known group types are {}, {} and {}", EXACT, AT_LEAST, AT_MOST ); From bd7b7f1d64534ca14bfa14eb8bada4d7f095b4c6 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 02:04:11 +0200 Subject: [PATCH 53/65] Add documentation --- .../src/generator/builder_generator.rs | 31 ++++++++++++++++++- .../src/generator/target_generator.rs | 4 ++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index e182149..a1df40b 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -92,7 +92,13 @@ impl<'a> BuilderGenerator<'a> { let vis = self.target_vis; + let documentation = format!( + "Builder for [`{}`] derived using the `const_typed_builder` crate", + self.target_name + ); + quote!( + #[doc = #documentation] #vis struct #builder_name #impl_generics #where_clause { #data_field: #data_name #type_generics } @@ -121,9 +127,14 @@ impl<'a> BuilderGenerator<'a> { let type_generics = self.generics_gen.const_generics_valued(false); let (impl_generics, _, where_clause) = self.generics_gen.target_generics().split_for_impl(); + let documentation = format!( + "Creates a new [`{}`] without any fields set", + self.builder_name + ); quote!( impl #impl_generics #builder_name #type_generics #where_clause{ + #[doc = #documentation] pub fn new() -> #builder_name #type_generics { Self::default() } @@ -131,6 +142,7 @@ impl<'a> BuilderGenerator<'a> { impl #impl_generics Default for #builder_name #type_generics #where_clause { fn default() -> Self { + #[doc = #documentation] #builder_name { #data_field: #data_name::default(), } @@ -144,6 +156,10 @@ impl<'a> BuilderGenerator<'a> { let builder_name = self.builder_name; let target_name = self.target_name; let data_field = self.data_field_ident(); + let documentation = format!( + "Build an instance of [`{}`], consuming the [`{}`]", + self.target_name, self.builder_name + ); let (impl_generics, target_type_generics, where_clause) = self.generics_gen.target_generics().split_for_impl(); @@ -159,7 +175,7 @@ impl<'a> BuilderGenerator<'a> { quote!( impl #impl_generics #builder_name #type_generics #where_clause{ - + #[doc = #documentation] pub fn build(self) -> #target_name #target_type_generics { self.#data_field.into() } @@ -194,6 +210,7 @@ impl<'a> BuilderGenerator<'a> { #correctness_verifier #correctness_helper_fns + #[doc = #documentation] pub fn build(self) -> #target_name #target_type_generics { #correctness_check self.#data_field.into() @@ -223,8 +240,20 @@ impl<'a> BuilderGenerator<'a> { let input_type = self.field_gen.builder_set_impl_input_type(field); let input_value = self.field_gen.builder_set_impl_input_value(field); + let documentation = format!(r#" +Setter for the [`{}::{field_name}`] field. + +# Arguments + +- `{field_name}`: field to be set + +# Returns + +`Self` with `{field_name}` initialized"#, self.target_name); + let tokens = quote!( impl #const_idents_impl #builder_name #const_idents_type_input #where_clause { + #[doc = #documentation] pub fn #field_name (self, #input_type) -> #builder_name #const_idents_type_output { let mut #data_field = self.#data_field; #data_field.#field_name = #input_value; diff --git a/const_typed_builder_derive/src/generator/target_generator.rs b/const_typed_builder_derive/src/generator/target_generator.rs index be71d25..7545bdd 100644 --- a/const_typed_builder_derive/src/generator/target_generator.rs +++ b/const_typed_builder_derive/src/generator/target_generator.rs @@ -48,14 +48,16 @@ impl<'a> TargetGenerator<'a> { let target_name = self.target_name; let builder_name = self.builder_name; let const_generics = self.generics_gen.const_generics_valued(false); - let _builder_generics = self.generics_gen.builder_struct_generics(); + // let _builder_generics = self.generics_gen.builder_struct_generics(); let (impl_generics, type_generics, where_clause) = self.generics_gen.target_generics().split_for_impl(); + let documentation = format!("Creates an instance of [`{}`]", self.builder_name); quote! { impl #impl_generics Builder for #target_name #type_generics #where_clause { type BuilderImpl = #builder_name #const_generics; + #[doc = #documentation] fn builder() -> Self::BuilderImpl { Self::BuilderImpl::new() } From 0281d5eef4202610fdc788793549d5188abc5e81 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 02:04:20 +0200 Subject: [PATCH 54/65] Hide FooData --- const_typed_builder_derive/src/generator/data_generator.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/const_typed_builder_derive/src/generator/data_generator.rs b/const_typed_builder_derive/src/generator/data_generator.rs index 9722aa1..0a867e3 100644 --- a/const_typed_builder_derive/src/generator/data_generator.rs +++ b/const_typed_builder_derive/src/generator/data_generator.rs @@ -67,6 +67,7 @@ impl<'a> DataGenerator<'a> { let tokens = quote!( impl #impl_generics From<#data_name #type_generics> for #struct_name #type_generics #where_clause { + #[doc(hidden)] fn from(data: #data_name #type_generics) -> #struct_name #type_generics { #struct_name { #(#from_fields),* @@ -75,6 +76,7 @@ impl<'a> DataGenerator<'a> { } impl #impl_generics Default for #data_name #type_generics #where_clause { + #[doc(hidden)] fn default() -> Self { #data_name { #def_fields @@ -94,6 +96,7 @@ impl<'a> DataGenerator<'a> { self.generics_gen.target_generics().split_for_impl(); let tokens = quote!( + #[doc(hidden)] pub struct #data_name #impl_generics #where_clause{ #(#fields),* } From 79e3ab0955b8261631fbaeaa4f291721a57e4394 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 02:05:04 +0200 Subject: [PATCH 55/65] Change a word --- const_typed_builder_derive/src/generator/builder_generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index a1df40b..86e9d92 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -128,7 +128,7 @@ impl<'a> BuilderGenerator<'a> { let type_generics = self.generics_gen.const_generics_valued(false); let (impl_generics, _, where_clause) = self.generics_gen.target_generics().split_for_impl(); let documentation = format!( - "Creates a new [`{}`] without any fields set", + "Creates a new [`{}`] without any fields initialized", self.builder_name ); From 63129f01266ccd399259e95ee8b6ddb92febeecc Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:22:48 +0200 Subject: [PATCH 56/65] Bump version --- Cargo.toml | 4 ++-- README.md | 2 +- const_typed_builder_test/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 996dfdc..51839bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "./const_typed_builder_derive", "./const_typed_builder_test"] [workspace.package] description = "Compile-time type-checked builder derive using const generics" -version = "0.2.0" +version = "0.3.0" authors = ["Koenichiwa "] repository = "https://github.com/koenichiwa/const_typed_builder/" edition = "2021" @@ -27,7 +27,7 @@ readme.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -const_typed_builder_derive = { path = "const_typed_builder_derive", version = "=0.2.0" } +const_typed_builder_derive = { path = "const_typed_builder_derive", version = "=0.3.0" } [dev-dependencies] trybuild = "1.0.84" diff --git a/README.md b/README.md index d0ae2f3..daefe59 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ To use the `Builder` derive macro, you should have the `const_typed_builder` cra ```toml [dependencies] -const_typed_builder = "0.2" +const_typed_builder = "0.3" ``` Also, make sure you have the following import statements in your code: diff --git a/const_typed_builder_test/Cargo.toml b/const_typed_builder_test/Cargo.toml index 6145395..203f16d 100644 --- a/const_typed_builder_test/Cargo.toml +++ b/const_typed_builder_test/Cargo.toml @@ -14,5 +14,5 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dev-dependencies] -const_typed_builder = { path = "../../const_typed_builder", version = "=0.2.0" } +const_typed_builder = { path = "../../const_typed_builder", version = "=0.3.0" } trybuild = "1.0.84" \ No newline at end of file From 900dea75900d9f45962d38291dedcfcf47c548ae Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:43:37 +0200 Subject: [PATCH 57/65] Update README Add #[builder(skip)] attribute Add explanation on why warnings are not emitted on stable channel Add small example --- README.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index daefe59..e66ad98 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # `Builder` Derive Macro Documentation -The `Builder` derive macro is used to generate builder methods for structs in Rust, its biggest feature in this crate is that it provides compile-time validation on the struct. The user can employ several configurations that define the validity of of a complex struct, and it is checked before the struct is ever created. +The `Builder` derive macro is used to generate builder methods for structs in Rust, its biggest feature in this crate is that it provides compile-time validation on the struct. The user can employ several configurations that define the validity of of a complex struct, and it is checked before the struct is ever created. + +The library also checks cases where the struct can never be valid or is always valid, but this is still in a work in progress. Errors are always emitted, but warnings are emitted on the nightly channel only. This is due to a limitation in [proc_macro_error](https://docs.rs/proc-macro-error/latest/proc_macro_error/). ## Prerequisites @@ -11,7 +13,7 @@ To use the `Builder` derive macro, you should have the `const_typed_builder` cra const_typed_builder = "0.3" ``` -Also, make sure you have the following import statements in your code: +Also, make sure you have the following use statement in your code: ```rust use const_typed_builder::Builder; @@ -96,25 +98,28 @@ let launchd = Launchd::builder() - `exact(N)`: Exactly N fields in the group must be set during the builder construction. - `at_least(N)`: At least N fields in the group must be set during the builder construction. - `at_most(N)`: At most N fields in the group can be set during the builder construction. - - `single`: Only one field in the group can be set during the builder construction. -- `#[builder(solver = )]`: **Use sparingly, see note at bottom of this file!** Specifies the solver type to be used for building the struct. The `solve_type` - should be one of the predefined solver types, such as `brute_force` or `compiler`. If provided with an equals sign (e.g., `#[builder(solver = brute_force)]`), - it sets the "solver type" accordingly. This attribute is still tested, and `brute_force` is the default, and only if there are problems in compilation time - then you can try `compiler`. `compiler` gives less guarantees though. + - `single`: Only one field in the group can be set during the builder construction. This is a shorthand for `exact(1)`. + e.g `#[group(foo = at_least(2))]` creates a group where at least 2 of the fields need to be initialized. +- `#[builder(solver = (brute_force|compiler))]`: **Use sparingly, see note at bottom of this file!** + Specifies the solver type to be used for building the struct. The `solve_type` should be one of the predefined solver types, such as `brute_force` or `compiler`. If provided with an equals sign (e.g., `#[builder(solver = brute_force)]`), + it sets the "solver type" accordingly. This attribute is still tested, and `brute_force` is the default, and only if there are problems in compilation time then you can try `compiler`. `compiler` gives less guarantees though. **Field** +- `#[builder(group = group_name)]`: The heart of this library. This associates the field with a group named `group_name`. + Fields in the same group are treated as a unit, and at least one of them must be set during builder construction. This attribute allows specifying the group name both as an identifier (e.g., `group = my_group`) + and as a string (e.g., `group = "my_group"`). - `#[builder(mandatory)]`: Marks the field as mandatory, meaning it must be set during the builder construction. If provided without an equals sign (e.g., `#[builder(mandatory)]`), it sets the field as mandatory. If provided with an equals sign (e.g., `#[builder(mandatory = true)]`), it sets the mandatory flag based on the value. - `#[builder(optional)]`: Marks the field as optional, this is the exact opposite of `#[builder(mandatory)]`. If provided without an equals sign (e.g., `#[builder(optional)]`), it sets the field as optional. If provided with an equals sign (e.g., `#[builder(optional = true)]`), it sets the optional flag based on the value. -- `#[builder(group = group_name)]`: Associates the field with a group named `group_name`. Fields in the same group - are treated as a unit, and at least one of them must be set during builder construction. If the field is marked as mandatory, - it cannot be part of a group. This attribute allows specifying the group name both as an identifier (e.g., `group = my_group`) - and as a string (e.g., `group = "my_group"`). -- `#[builder(propagate)]`: Indicates that the field should propagate its value when the builder is constructed. If this attribute - is present, the field's value will be copied or moved to the constructed object when the builder is used to build the object. +- `#[builder(skip)]`: Marks the field as skipped, meaning that the builder will not include it. This can be used for + fields that are deprecated, but must still be deserialized. This way you can ensure that new structs will never be created with this field initialized, but that old structs can still be used. The field type has to be `Option` for this to work. +- `#[builder(propagate)]`: Indicates that the field should propagate its value when the builder is constructed. + If this attribute is present, the field's value will be copied or moved to the constructed object when the builder is used to build the object. + +Fields can either be a part of a group, mandatory, optional OR skipped. These attribute properties are mutually exclusive. `propagate` can be used on any field where the type also derives `Builder`. > [!NOTE] > Checking the validity of each field is a problem directly related to [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem), which is an NP-complete problem. This has effect especially the grouped fields. The current default implementation for checking the validity of grouped fields is `brute_force`, and this implementation currently has a complexity of $`O(2^g)`$ where $`g`$ is the amount of grouped variables. This is not a problem with a couple of fields, but it might impact compile time significantly with more fields. This can be still optimized significantly. Future editions might improve on this complexity. From cf1425a1a608eebdf8f49531e0a91955e24423fa Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 10:45:51 +0200 Subject: [PATCH 58/65] Remove `Debug` from README examples --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e66ad98..cd1ebb0 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Basic usage: ```rust use const_typed_builder::Builder; -#[derive(Debug, Builder)] +#[derive(Builder)] pub struct Foo { bar: String, } @@ -42,12 +42,12 @@ Subset of launchd: use const_typed_builder::Builder; use std::path::PathBuf; -#[derive(Debug, Builder)] +#[derive(Builder)] pub struct ResourceLimits { core: Option, // ... } -#[derive(Debug, Builder)] +#[derive(Builder)] #[group(program = at_least(1))] pub struct Launchd { #[builder(mandatory)] From 45fae6e390cef2ffa77ec3d6e1f2264cf69d1797 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:04:21 +0200 Subject: [PATCH 59/65] Update documentation on the generator module --- .../src/generator/builder_generator.rs | 2 +- .../src/generator/data_generator.rs | 2 +- .../src/generator/field_generator.rs | 8 ++++---- .../src/generator/group_generator.rs | 2 +- const_typed_builder_derive/src/generator/mod.rs | 2 +- .../src/generator/target_generator.rs | 1 - 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 86e9d92..03bf7f6 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -68,7 +68,7 @@ impl<'a> BuilderGenerator<'a> { /// /// # Returns /// - /// A `StreamResult` representing the generated code for the builder struct and methods. + /// A `Tokenstream` representing the generated code for the builder struct and methods. pub fn generate(&self) -> TokenStream { let builder_struct = self.generate_struct(); let builder_impl = self.generate_impl(); diff --git a/const_typed_builder_derive/src/generator/data_generator.rs b/const_typed_builder_derive/src/generator/data_generator.rs index 0a867e3..8127f1c 100644 --- a/const_typed_builder_derive/src/generator/data_generator.rs +++ b/const_typed_builder_derive/src/generator/data_generator.rs @@ -42,7 +42,7 @@ impl<'a> DataGenerator<'a> { /// /// # Returns /// - /// A `StreamResult` representing the generated code for the data struct and conversions. + /// A `TokenStream` representing the generated code for the data struct and conversions. pub fn generate(&self) -> TokenStream { let data_struct = self.generate_struct(); let data_impl = self.generate_impl(); diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index daf6477..2382bd9 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -31,7 +31,7 @@ impl<'a> FieldGenerator<'a> { /// /// # Returns /// - /// A `VecStreamResult` representing the generated code for the data struct fields. + /// A `Vec` representing the data struct fields: `pub field_name: field_type`. pub fn data_struct_fields(&self) -> Vec { self.fields .iter() @@ -61,7 +61,7 @@ impl<'a> FieldGenerator<'a> { /// /// # Returns /// - /// A `VecStreamResult` representing the generated code for the `From` trait implementation. + /// A `Vec` representing the fields for the `From` trait implementation. Either containing `unwrap`, `None` or just the type. pub fn data_impl_from_fields(&self) -> Vec { self.fields .iter() @@ -111,7 +111,7 @@ impl<'a> FieldGenerator<'a> { /// /// # Returns /// - /// A `TokenStream` representing the generated input type for the builder setter method. + /// A `Option` representing the generated input type for the builder setter method. None if the field is skipped. pub fn builder_set_impl_input_type(&self, field: &'a FieldInfo) -> Option { if field.kind() == &FieldKind::Skipped { return None; @@ -141,7 +141,7 @@ impl<'a> FieldGenerator<'a> { /// /// # Returns /// - /// A `TokenStream` representing the generated input value for the builder setter method. + /// A `Option` representing the generated input value for the builder setter method. None if the field is skipped. pub fn builder_set_impl_input_value(&self, field: &'a FieldInfo) -> Option { if field.kind() == &FieldKind::Skipped { return None; diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index 12fc24e..edd662e 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -14,7 +14,7 @@ pub(super) struct GroupGenerator<'a> { } impl<'a> GroupGenerator<'a> { - /// Creates a new `GroupGenerator` instance. + /// Creates a new `GroupGenerator` instance, first checking if the groups can be valid. /// /// # Arguments /// diff --git a/const_typed_builder_derive/src/generator/mod.rs b/const_typed_builder_derive/src/generator/mod.rs index 7e1bcc9..65f41d2 100644 --- a/const_typed_builder_derive/src/generator/mod.rs +++ b/const_typed_builder_derive/src/generator/mod.rs @@ -64,7 +64,7 @@ impl<'a> Generator<'a> { /// /// # Returns /// - /// A `StreamResult` representing the generated token stream. + /// A `TokenStream` representing the generated token stream. pub fn generate(&self) -> TokenStream { let target = self.target_gen.generate(); let data = self.data_gen.generate(); diff --git a/const_typed_builder_derive/src/generator/target_generator.rs b/const_typed_builder_derive/src/generator/target_generator.rs index 7545bdd..f9a8d9d 100644 --- a/const_typed_builder_derive/src/generator/target_generator.rs +++ b/const_typed_builder_derive/src/generator/target_generator.rs @@ -48,7 +48,6 @@ impl<'a> TargetGenerator<'a> { let target_name = self.target_name; let builder_name = self.builder_name; let const_generics = self.generics_gen.const_generics_valued(false); - // let _builder_generics = self.generics_gen.builder_struct_generics(); let (impl_generics, type_generics, where_clause) = self.generics_gen.target_generics().split_for_impl(); From 02ff3d214fbed78109a04ad7c2d88dccad0a037a Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:11:14 +0200 Subject: [PATCH 60/65] Update return type in documentation. The implementation might not return a tokenstream This happens when there where errors emitted --- const_typed_builder_derive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index f9ffce9..915df34 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -48,7 +48,7 @@ pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// /// # Returns /// -/// A `StreamResult` representing the generated token stream. +/// An optional `TokenStream` representing the generated token stream. fn impl_my_derive(ast: &syn::DeriveInput) -> Option { let struct_info = StructInfo::new(ast)?; let generator = Generator::new(&struct_info); From 58d968cbee28d6e4ff3b2e81377b92530e4b8c2a Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:25:39 +0200 Subject: [PATCH 61/65] Update info module documentation --- .../src/info/field_info.rs | 7 ++----- .../src/info/group_info.rs | 3 +++ .../src/info/struct_info.rs | 20 +++++++------------ 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs index 5e8b5fe..a8313fd 100644 --- a/const_typed_builder_derive/src/info/field_info.rs +++ b/const_typed_builder_derive/src/info/field_info.rs @@ -43,7 +43,7 @@ impl<'a> FieldInfo<'a> { /// /// # Returns /// - /// A `Result` containing the `FieldInfo` instance or an error if the field is unnamed. + /// An otpional `FieldInfo` instance if successful. pub fn new( field: &'a syn::Field, struct_settings: &mut StructSettings, @@ -193,6 +193,7 @@ impl<'a> Ord for FieldInfo<'a> { /// Represents settings for struct field generation. #[derive(Debug, Clone)] pub struct FieldSettings { + /// Indicates that this field is skipped. pub skipped: bool, /// Indicates if the field is mandatory. pub mandatory: bool, @@ -277,10 +278,6 @@ impl FieldSettings { /// # Arguments /// /// - `attr`: A reference to the `syn::Attribute` representing the builder attribute applied to the field. - /// - /// # Returns - /// - /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) { let attr_ident = match attr.path().require_ident() { Ok(ident) if ident == BUILDER => ident, diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group_info.rs index 6447190..43242fa 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group_info.rs @@ -44,10 +44,12 @@ impl GroupInfo { } } + /// Associate a field index with this group pub fn associate(&mut self, index: usize) -> bool { self.associated_indices.insert(index) } + /// Retrieves all associated indices pub fn indices(&self) -> &BTreeSet { &self.associated_indices } @@ -78,6 +80,7 @@ impl GroupInfo { } } + /// Check if the group is formed correctly. Will emit errors or warnings if invalid. pub fn check(&self) { let valid_range = 1..self.indices().len(); if valid_range.is_empty() { diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/info/struct_info.rs index 35a1492..f8f638d 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/info/struct_info.rs @@ -35,6 +35,7 @@ pub struct StructInfo<'a> { groups: HashMap, /// A collection of `FieldInfo` instances representing struct fields. field_infos: FieldInfos<'a>, + /// The solver used to find all possible valid combinations for the groups solve_type: SolveType, } @@ -47,7 +48,7 @@ impl<'a> StructInfo<'a> { /// /// # Returns /// - /// A `syn::Result` containing the `StructInfo` instance if successful, + /// An optional `StructInfo` instance if successful, pub fn new(ast: &'a syn::DeriveInput) -> Option { match &ast { syn::DeriveInput { @@ -129,6 +130,7 @@ impl<'a> StructInfo<'a> { &self.groups } + /// Retrieves the solver type used to find all possible valid combinations for the groups pub fn solve_type(&self) -> SolveType { self.solve_type } @@ -145,7 +147,9 @@ pub struct StructSettings { default_field_settings: FieldSettings, /// A map of group names to their respective `GroupInfo`. groups: HashMap, + /// The indices of the mandatory fields mandatory_indices: BTreeSet, + /// The solver used to find all possible valid combinations for the groups solver_type: SolveType, } @@ -168,10 +172,12 @@ impl StructSettings { StructSettings::default() } + /// Add a field index to the set of mandatory indices pub fn add_mandatory_index(&mut self, index: usize) -> bool { self.mandatory_indices.insert(index) } + /// Get a GroupInfo by its identifier pub fn group_by_name_mut(&mut self, group_name: &String) -> Option<&mut GroupInfo> { self.groups.get_mut(group_name) } @@ -202,10 +208,6 @@ impl StructSettings { /// /// # Arguments /// /// - `attr`: A reference to the `syn::Attribute` representing the builder attribute applied to the struct. - /// - /// # Returns - /// - /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_attribute(&mut self, attr: &syn::Attribute) { let attr_ident = match attr.path().require_ident() { Ok(ident) => ident, @@ -252,10 +254,6 @@ impl StructSettings { /// # Arguments /// /// - `attr`: A reference to the `syn::Attribute` representing the builder attribute applied to the struct. - /// - /// # Returns - /// - /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_builder_attribute(&mut self, attr: &syn::Attribute) { attr.parse_nested_meta(|meta| { let path_ident = match meta.path.require_ident() { @@ -335,10 +333,6 @@ impl StructSettings { /// # Arguments /// /// - `attr`: A reference to the `syn::Attribute` representing the group attribute applied to the struct. - /// - /// # Returns - /// - /// A `Result` indicating success or failure in handling the attribute. Errors are returned for invalid or conflicting attributes. fn handle_group_attribute(&mut self, attr: &syn::Attribute) { attr.parse_nested_meta(|meta| { let group_name = match meta.path.require_ident() { From b7f4230fe435be5f45cf868febae85d231020b86 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:34:36 +0200 Subject: [PATCH 62/65] Update top level documentation --- src/lib.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1d699cc..8836e4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,11 +40,12 @@ /// .build(); /// ``` /// -/// ## 2. Mandatory and Optional Fields +/// ## 2. Mandatory, Skipped and Optional Fields /// /// By default, all fields in the generated builder are considered mandatory, meaning they must be provided during construction. /// However, fields with the `Option` type are considered optional and can be left unset, and thus defaulted to `None`. /// +/// Fields can be either Mandatory, Skipped, Optional or Grouped (see next subsection), these are mutually exclusive. /// ### Example: /// Valid construction with optional field left unset /// ```rust @@ -92,10 +93,26 @@ /// } /// let foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!".to_string()).build(); /// ``` +/// +/// You can also skip fields. This can be used if you still want to deserialize deprecated fields for instance. +/// ```compile_fail +/// # use const_typed_builder::Builder; +/// #[derive(Debug, Builder)] +/// pub struct Foo { +/// bar: String, // Mandatory +/// #[builder(skip)] +/// baz: Option, // Skipped. The builder will leave it as `None`. +/// } /// -/// ## 3. Grouping Fields +/// let foo = Foo::builder() +/// .bar("Hello".to_string()) +/// .baz("World".to_string()) // This function does not exist +/// .build(); +/// ``` +/// ## 3. Grouped Fields /// -/// Fields can be grouped together, and constraints can be applied to these groups. Groups allow you to ensure that a certain combination of fields is provided together. +/// Fields can be grouped together, and constraints can be applied to these groups. +/// Groups allow you to ensure that a certain combination of fields is provided together. /// There are four types of groups: `single`, `at_least`, `at_most`, and `exact`. /// /// **All** fields that are grouped need to be an `Option` type. @@ -104,6 +121,10 @@ /// - `at_least(n)`: Requires at least `n` fields in the group to be provided. /// - `at_most(n)`: Allows at most `n` fields in the group to be provided. /// - `exact(n)`: Requires exactly `n` fields in the group to be provided. +/// +/// The range of n is 1..=k, where k is the amount of fields that are associated with this group. +/// Errors will be emitted if the group can never be valid, or can be replaced by `skip` or `mandatory`. +/// Warnings will be emitted if the group is always valid, or can be replaced by `optional`. /// /// ### Examples: /// From f7d40cbb24c4ee34c5581ad7de90a502165ee59b Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:40:23 +0200 Subject: [PATCH 63/65] Link to `Builder` in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cd1ebb0..02c2649 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ let launchd = Launchd::builder() ``` ### Attributes +This is a quick overview of the features in this library. See [`const_typed_builder_derive::Builder`] for a more in depth explanation of all the features, including examples. **Struct** - `#[builder(assume_mandatory)]`: Indicates that all fields in the struct should be assumed as mandatory. If provided without an equals sign (e.g., `#[builder(assume_mandatory)]`), it sets the `mandatory` flag for fields to true. From 85efb20e766277e9e0cc07f4727dc63296ddfbc4 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:44:36 +0200 Subject: [PATCH 64/65] Remove `Debug` from examples --- src/lib.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8836e4e..66c0301 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ /// Basic example /// ```rust /// use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// pub struct Foo { /// bar: String, /// baz: String, @@ -29,7 +29,7 @@ /// This will not compile without providing 'baz' /// ```compile_fail /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// pub struct Foo { /// bar: String, /// baz: String, @@ -50,7 +50,7 @@ /// Valid construction with optional field left unset /// ```rust /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// pub struct Foo { /// bar: String, // Mandatory /// baz: Option, // Optional @@ -67,7 +67,7 @@ /// Invalid construction with optional field left unset: /// ```compile_fail /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// pub struct Foo { /// bar: String, // Mandatory /// #[builder(mandatory)] @@ -83,7 +83,7 @@ /// /// ``` /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// #[builder(assume_mandatory)] /// pub struct Foo { /// bar: Option, @@ -97,7 +97,7 @@ /// You can also skip fields. This can be used if you still want to deserialize deprecated fields for instance. /// ```compile_fail /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// pub struct Foo { /// bar: String, // Mandatory /// #[builder(skip)] @@ -131,7 +131,7 @@ /// Valid construction only one field in `my_group` is provided /// ```rust /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// #[group(my_group = single)] /// pub struct Foo { /// #[builder(group = my_group)] @@ -147,7 +147,7 @@ /// Invalid construction because both fields in `my_group` are provided /// ```compile_fail /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// #[group(my_group = single)] /// pub struct Foo { /// #[builder(group = my_group)] @@ -164,7 +164,7 @@ /// Valid construction because at least 2 fields in `my_group` are provided: /// ```rust /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// #[group(my_group = at_least(2))] /// pub struct Foo { /// #[builder(group = my_group)] @@ -185,7 +185,7 @@ /// Valid construction because at least 2 fields in 'least' are provided, and 'fred' had to be provided to validate the group 'most': /// ```rust /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// #[group(least = at_least(2))] /// #[group(most = at_most(3))] /// pub struct Foo { @@ -218,13 +218,13 @@ /// Valid construction with complex struct 'Bar' created within 'Foo' /// ```rust /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// pub struct Foo { /// #[builder(propagate)] /// bar: Bar, /// } /// -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// pub struct Bar { /// baz: String, /// } @@ -244,7 +244,7 @@ /// Valid construction for a generic struct 'Foo' with a default generic parameter /// ```rust /// # use const_typed_builder::Builder; -/// #[derive(Debug, Builder)] +/// #[derive(Builder)] /// pub struct Foo /// where /// A: Into, From 41b664939f47246f1625ce993284546498e402a7 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:45:03 +0200 Subject: [PATCH 65/65] fmt --- src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 66c0301..4298f62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ /// } /// let foo = Foo::builder().bar("Hello world!".to_string()).baz("Hello world!".to_string()).build(); /// ``` -/// +/// /// You can also skip fields. This can be used if you still want to deserialize deprecated fields for instance. /// ```compile_fail /// # use const_typed_builder::Builder; @@ -111,7 +111,7 @@ /// ``` /// ## 3. Grouped Fields /// -/// Fields can be grouped together, and constraints can be applied to these groups. +/// Fields can be grouped together, and constraints can be applied to these groups. /// Groups allow you to ensure that a certain combination of fields is provided together. /// There are four types of groups: `single`, `at_least`, `at_most`, and `exact`. /// @@ -121,9 +121,9 @@ /// - `at_least(n)`: Requires at least `n` fields in the group to be provided. /// - `at_most(n)`: Allows at most `n` fields in the group to be provided. /// - `exact(n)`: Requires exactly `n` fields in the group to be provided. -/// -/// The range of n is 1..=k, where k is the amount of fields that are associated with this group. -/// Errors will be emitted if the group can never be valid, or can be replaced by `skip` or `mandatory`. +/// +/// The range of n is 1..=k, where k is the amount of fields that are associated with this group. +/// Errors will be emitted if the group can never be valid, or can be replaced by `skip` or `mandatory`. /// Warnings will be emitted if the group is always valid, or can be replaced by `optional`. /// /// ### Examples: