From 16b15c1e535d744aecbf85d812965344c4629619 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:53:42 +0200 Subject: [PATCH 01/15] Start refactor Split into three stages: parser, info, and generator remove skipped and mandatory booleans and replace them with Option Change StructInfo to info::Container (because future enum) Place util, fieldkind and solverkind in their own files --- const_typed_builder_derive/src/field_kind.rs | 7 + .../src/generator/builder_generator.rs | 10 +- .../src/generator/field_generator.rs | 12 +- .../src/generator/generics_generator.rs | 10 +- .../src/generator/mod.rs | 8 +- .../src/info/container.rs | 129 +++++ const_typed_builder_derive/src/info/field.rs | 158 ++++++ .../src/info/field_info.rs | 464 ------------------ const_typed_builder_derive/src/info/mod.rs | 8 +- const_typed_builder_derive/src/lib.rs | 8 +- .../struct_info.rs => parser/container.rs} | 243 ++------- .../src/parser/field.rs | 249 ++++++++++ const_typed_builder_derive/src/parser/mod.rs | 5 + const_typed_builder_derive/src/solver_kind.rs | 5 + const_typed_builder_derive/src/util.rs | 35 ++ .../compile_fail/group_and_mandatory_3.stderr | 21 - .../compile_fail/group_and_mandatory_4.stderr | 21 - .../compile_fail/optional_mandatory_set_1.rs | 2 +- const_typed_builder_test/src/lib.rs | 2 +- 19 files changed, 673 insertions(+), 724 deletions(-) create mode 100644 const_typed_builder_derive/src/field_kind.rs create mode 100644 const_typed_builder_derive/src/info/container.rs create mode 100644 const_typed_builder_derive/src/info/field.rs delete mode 100644 const_typed_builder_derive/src/info/field_info.rs rename const_typed_builder_derive/src/{info/struct_info.rs => parser/container.rs} (63%) create mode 100644 const_typed_builder_derive/src/parser/field.rs create mode 100644 const_typed_builder_derive/src/parser/mod.rs create mode 100644 const_typed_builder_derive/src/solver_kind.rs create mode 100644 const_typed_builder_derive/src/util.rs delete mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr delete mode 100644 const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr diff --git a/const_typed_builder_derive/src/field_kind.rs b/const_typed_builder_derive/src/field_kind.rs new file mode 100644 index 0000000..aab5b79 --- /dev/null +++ b/const_typed_builder_derive/src/field_kind.rs @@ -0,0 +1,7 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum FieldKind { + Optional, + Skipped, + Mandatory, + Grouped, +} diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 03bf7f6..776b4e4 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::info::{FieldKind, SolveType}; +use crate::{field_kind::FieldKind, solver_kind::SolverKind}; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -17,7 +17,7 @@ pub(super) struct BuilderGenerator<'a> { target_vis: &'a syn::Visibility, builder_name: &'a syn::Ident, data_name: &'a syn::Ident, - solve_type: SolveType, + solve_type: SolverKind, } impl<'a> BuilderGenerator<'a> { @@ -46,7 +46,7 @@ impl<'a> BuilderGenerator<'a> { target_vis: &'a syn::Visibility, builder_name: &'a syn::Ident, data_name: &'a syn::Ident, - solve_type: SolveType, + solve_type: SolverKind, ) -> Self { Self { group_gen, @@ -164,7 +164,7 @@ impl<'a> BuilderGenerator<'a> { self.generics_gen.target_generics().split_for_impl(); match self.solve_type { - SolveType::BruteForce => { + SolverKind::BruteForce => { let build_impls = self.group_gen .valid_groupident_combinations() @@ -187,7 +187,7 @@ impl<'a> BuilderGenerator<'a> { #(#build_impls)* ) } - SolveType::Compiler => { + SolverKind::Compiler => { let builder_name = self.builder_name; let impl_generics = self .generics_gen diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index 2382bd9..ebf3f71 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -1,11 +1,11 @@ -use crate::info::{FieldInfo, FieldKind}; +use crate::{field_kind::FieldKind, info::Field}; 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>], + fields: &'a [Field<'a>], } impl<'a> FieldGenerator<'a> { @@ -18,12 +18,12 @@ impl<'a> FieldGenerator<'a> { /// # Returns /// /// A `FieldGenerator` instance initialized with the provided fields. - pub fn new(fields: &'a [FieldInfo]) -> Self { + pub fn new(fields: &'a [Field]) -> Self { Self { fields } } /// Returns a reference to the fields of the struct. - pub fn fields(&self) -> &[FieldInfo] { + pub fn fields(&self) -> &[Field] { self.fields } @@ -112,7 +112,7 @@ impl<'a> FieldGenerator<'a> { /// # Returns /// /// 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 { + pub fn builder_set_impl_input_type(&self, field: &'a Field) -> Option { if field.kind() == &FieldKind::Skipped { return None; } @@ -142,7 +142,7 @@ impl<'a> FieldGenerator<'a> { /// # Returns /// /// 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 { + pub fn builder_set_impl_input_value(&self, field: &'a Field) -> Option { if field.kind() == &FieldKind::Skipped { return None; } diff --git a/const_typed_builder_derive/src/generator/generics_generator.rs b/const_typed_builder_derive/src/generator/generics_generator.rs index ab652d3..106166f 100644 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ b/const_typed_builder_derive/src/generator/generics_generator.rs @@ -1,4 +1,4 @@ -use crate::{info::FieldInfo, info::FieldKind}; +use crate::{field_kind::FieldKind, info::Field}; use either::Either; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -7,7 +7,7 @@ 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>], + pub fields: &'a [Field<'a>], target_generics: &'a syn::Generics, } @@ -22,7 +22,7 @@ pub(super) struct GenericsGenerator<'a> { /// /// 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 { + pub fn new(fields: &'a [Field], target_generics: &'a syn::Generics) -> Self { Self { fields, target_generics, @@ -66,7 +66,7 @@ impl<'a> GenericsGenerator<'a> { /// 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, + field_info: &Field, value: bool, ) -> TokenStream { let mut all = self.fields.iter().filter_map(|field| match field.kind() { @@ -89,7 +89,7 @@ impl<'a> GenericsGenerator<'a> { /// # 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 { + pub fn builder_const_generic_idents_set_impl(&self, field_info: &Field) -> syn::Generics { let mut all = self.fields.iter().filter_map(|field| match field.kind() { FieldKind::Skipped | FieldKind::Optional => None, _ if field == field_info => None, diff --git a/const_typed_builder_derive/src/generator/mod.rs b/const_typed_builder_derive/src/generator/mod.rs index 65f41d2..2d5690a 100644 --- a/const_typed_builder_derive/src/generator/mod.rs +++ b/const_typed_builder_derive/src/generator/mod.rs @@ -10,7 +10,7 @@ use self::{ field_generator::FieldGenerator, generics_generator::GenericsGenerator, group_generator::GroupGenerator, target_generator::TargetGenerator, }; -use crate::info::StructInfo; +use crate::info::Container; use proc_macro2::TokenStream; use quote::quote; @@ -31,9 +31,9 @@ impl<'a> Generator<'a> { /// # 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()); + pub fn new(info: &'a Container<'a>) -> Self { + let generics_gen = GenericsGenerator::new(info.field_collection(), info.generics()); + let field_gen = FieldGenerator::new(info.field_collection()); let group_gen = GroupGenerator::new(info.groups().values().collect()); Generator { data_gen: DataGenerator::new( diff --git a/const_typed_builder_derive/src/info/container.rs b/const_typed_builder_derive/src/info/container.rs new file mode 100644 index 0000000..b75a06c --- /dev/null +++ b/const_typed_builder_derive/src/info/container.rs @@ -0,0 +1,129 @@ +use super::field::Field; +use super::group_info::GroupInfo; +use crate::{parser, solver_kind::SolverKind}; + +use proc_macro_error::emit_error; +use quote::format_ident; +use std::collections::{BTreeSet, HashMap}; + +/// A type alias for a collection of `FieldInfo` instances. +type FieldCollection<'a> = Vec>; + +/// Represents the information about a struct used for code generation. +#[derive(Debug)] +pub struct Container<'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, + _mandatory_indices: BTreeSet, + /// A map of group names to their respective `GroupInfo`. + groups: HashMap, + /// A collection of `FieldInfo` instances representing struct fields. + field_collection: FieldCollection<'a>, + /// The solver used to find all possible valid combinations for the groups + solve_type: SolverKind, +} + +impl<'a> Container<'a> { + /// Creates a new `StructInfo` instance from a `syn::DeriveInput`. + /// + /// # Arguments + /// + /// - `ast`: A `syn::DeriveInput` representing the input struct. + /// + /// # Returns + /// + /// An optional `StructInfo` instance if successful, + 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 = parser::Container::default().with_attrs(attrs); + + let field_infos = fields + .named + .iter() + .enumerate() + .map(|(index, field)| Field::new(field, &mut settings, index)) + .collect::>>()?; + + let info = Container { + ident, + vis, + generics, + builder_ident: format_ident!("{}{}", ident, "Builder"), + data_ident: format_ident!("{}{}", ident, "Data"), + _mandatory_indices: settings.mandatory_indices().clone(), + groups: settings.groups().clone(), + field_collection: field_infos, + solve_type: settings.solver_kind(), + }; + Some(info) + } + _ => { + emit_error!(ast, "Builder is only supported for named structs",); + None + } + } + } + + /// 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_collection(&self) -> &FieldCollection { + &self.field_collection + } + + /// Retrieves a reference to the map of group names to their respective `GroupInfo`. + pub fn groups(&self) -> &HashMap { + &self.groups + } + + /// Retrieves the solver type used to find all possible valid combinations for the groups + pub fn solve_type(&self) -> SolverKind { + self.solve_type + } +} diff --git a/const_typed_builder_derive/src/info/field.rs b/const_typed_builder_derive/src/info/field.rs new file mode 100644 index 0000000..1657d83 --- /dev/null +++ b/const_typed_builder_derive/src/info/field.rs @@ -0,0 +1,158 @@ +use crate::{ + field_kind::FieldKind, + parser, symbol, + util::{inner_type, is_option}, + CONST_IDENT_PREFIX, +}; +use proc_macro_error::emit_error; +use quote::format_ident; + +/// Represents the information about a struct field used for code generation. +#[derive(Debug, PartialEq, Eq)] +pub struct Field<'a> { + field: &'a syn::Field, + ident: &'a syn::Ident, + index: usize, + propagate: bool, + 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, +// /// Indicates a field that not included in the builder. +// Skipped, +// } + +impl<'a> Field<'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 + /// + /// An otpional `FieldInfo` instance if successful. + pub fn new( + field: &'a syn::Field, + struct_parser: &mut parser::Container, + index: usize, + ) -> Option { + if let syn::Field { + ident: Some(ident), .. + } = field + { + let field_parser = + parser::Field::default().handle_field(field, struct_parser.assume_mandatory()); + let kind = field_parser + .kind() + .expect("Kind should always be set after parsing"); + + match kind { + FieldKind::Optional | FieldKind::Skipped => {}, + FieldKind::Mandatory => assert!(struct_parser.add_mandatory_index(index)), + FieldKind::Grouped => field_parser.groups().iter().for_each(|group_name| { + if let Some(group) = struct_parser.group_by_name_mut(&group_name.to_string()) + { + group.associate(index); + } else { + emit_error!( + group_name, + "No group called {} is available", group_name; + hint = "You might want to add a #[{}(...)] attribute to the container", symbol::GROUP + ); + } + }), + }; + + Some(Self { + field, + ident, + index, + propagate: field_parser.propagate(), + kind, + }) + } else { + emit_error!(field, "Unnamed fields are not supported"); + None + } + } + + /// 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 + } + + /// Checks if the field's type is an Option. + pub fn is_option_type(&self) -> bool { + is_option(&self.field.ty) + } + + /// Retrieves the type of the field. + pub fn ty(&self) -> &syn::Type { + &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) + } + + /// Retrieves the input type for the builder's setter method. + pub fn setter_input_type(&self) -> Option<&syn::Type> { + match self.kind() { + 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 => 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, + } + } +} + +impl<'a> PartialOrd for Field<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + self.index.partial_cmp(&other.index) + } +} + +impl<'a> Ord for Field<'a> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.index.cmp(&other.index) + } +} diff --git a/const_typed_builder_derive/src/info/field_info.rs b/const_typed_builder_derive/src/info/field_info.rs deleted file mode 100644 index a8313fd..0000000 --- a/const_typed_builder_derive/src/info/field_info.rs +++ /dev/null @@ -1,464 +0,0 @@ -use super::struct_info::StructSettings; -use crate::{ - symbol::{BUILDER, GROUP, MANDATORY, OPTIONAL, PROPAGATE, SKIP}, - CONST_IDENT_PREFIX, -}; -use proc_macro2::Span; -use proc_macro_error::{emit_error, emit_warning}; -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)] -pub struct FieldInfo<'a> { - field: &'a syn::Field, - ident: &'a syn::Ident, - index: usize, - propagate: bool, - 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, - /// Indicates a field that not included in the builder. - Skipped, -} - -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 - /// - /// An otpional `FieldInfo` instance if successful. - pub fn new( - field: &'a syn::Field, - struct_settings: &mut StructSettings, - index: usize, - ) -> Option { - if let syn::Field { - attrs, - ident: Some(ident), - ty, - vis: _, - mutability: _, - colon_token: _, - } = field - { - let settings = struct_settings - .default_field_settings() - .clone() - .with_ty(ty) - .with_attrs(attrs) - .ok()?; - - 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, - ident, - index, - propagate: settings.propagate, - kind: FieldKind::Mandatory, - } - } else if !settings.groups.is_empty() { - for group_name in settings.groups { - if let Some(group) = struct_settings.group_by_name_mut(&group_name.to_string()) - { - group.associate(index); - } else { - 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") - ); - } - } - - Self { - field, - ident, - index, - propagate: settings.propagate, - kind: FieldKind::Grouped, - } - } else { - Self { - field, - ident, - index, - propagate: settings.propagate, - kind: FieldKind::Optional, - } - }; - - Some(info) - } else { - emit_error!(field, "Unnamed fields are not supported"); - None - } - } - - /// 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 - } - - /// Checks if the field's type is an Option. - pub fn is_option_type(&self) -> bool { - util::is_option(&self.field.ty) - } - - /// Retrieves the type of the field. - pub fn ty(&self) -> &syn::Type { - &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> { - util::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) - } - - /// Retrieves the input type for the builder's setter method. - pub fn setter_input_type(&self) -> Option<&syn::Type> { - match self.kind() { - 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 => 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, - } - } -} - -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 { - /// Indicates that this field is skipped. - pub skipped: bool, - /// 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, - /// The groups this field belongs to. - pub groups: HashSet, -} - -impl Default for FieldSettings { - fn default() -> FieldSettings { - FieldSettings { - skipped: false, - mandatory: false, - propagate: false, - input_name: syn::Ident::new("input", Span::call_site()), - groups: HashSet::new(), - } - } -} - -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. - fn with_attrs(mut self, attrs: &[syn::Attribute]) -> syn::Result { - attrs.iter().for_each(|attr| self.handle_attribute(attr)); - // .collect::, _>>()?; - 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. - fn with_ty(mut self, ty: &syn::Type) -> Self { - if !self.mandatory && !util::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. - fn handle_attribute(&mut self, attr: &syn::Attribute) { - 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(), "Can't parse attribute"; - note = err - ); - return; - } - }; - - match attr.meta.require_list() { - Ok(list) => { - if list.tokens.is_empty() { - emit_warning!(list, "Empty atrribute list"); - } - } - 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, "Specifier cannot be parsed"; - help = "Try specifying it like #[{}(specifier)]", attr_ident; - note = err - ); - return Ok(()); - } - }; - - 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; - } - } - 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; - } - } - 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; - } - } - GROUP => { - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - let group_name = match expr { - syn::Expr::Path(ExprPath { path, .. }) => { - match path.require_ident() { - Ok(ident) => ident, - Err(err) => { - emit_error!( - path, "Group name not specified correctly"; - help = "Try defining it like #[{}(foo)]", BUILDER; - note = err - ); - return Ok(()); - } - }.clone() - } - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }) => { - syn::Ident::new(lit.value().as_str(), lit.span()) - } - expr => { - 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"; - help = self.groups.get(&group_name).unwrap().span() => "Remove this attribute" - ); - } else { - self.groups.insert(group_name); - } - } - } - PROPAGATE => { - self.propagate = true; - } - _ => { - emit_error!(&attr.meta, "Unknown attribute") - } - } - - if self.mandatory && !self.groups.is_empty() { - emit_error!( - &meta.path, - format!("Can't use both {MANDATORY} and {GROUP} attributes"); - hint = "Remove either types of attribute from this field" - ); - } - Ok(()) - }) - .unwrap_or_else(|err| emit_error!( - &attr.meta, "Unknown error"; - note = err - )) - } -} - -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/info/mod.rs b/const_typed_builder_derive/src/info/mod.rs index 17eed71..fd234ec 100644 --- a/const_typed_builder_derive/src/info/mod.rs +++ b/const_typed_builder_derive/src/info/mod.rs @@ -1,7 +1,7 @@ -mod field_info; +mod container; +mod field; mod group_info; -mod struct_info; -pub use field_info::{FieldInfo, FieldKind}; +pub use container::Container; +pub use field::Field; pub use group_info::{GroupInfo, GroupType}; -pub use struct_info::{SolveType, StructInfo}; diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index 915df34..21f7a91 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -1,9 +1,13 @@ +mod field_kind; mod generator; mod info; +mod parser; +mod solver_kind; mod symbol; +mod util; use generator::Generator; -use info::StructInfo; +use info::Container; use proc_macro2::TokenStream; use proc_macro_error::proc_macro_error; use quote::quote; @@ -50,7 +54,7 @@ pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// /// An optional `TokenStream` representing the generated token stream. fn impl_my_derive(ast: &syn::DeriveInput) -> Option { - let struct_info = StructInfo::new(ast)?; + let struct_info = Container::new(ast)?; let generator = Generator::new(&struct_info); Some(generator.generate()) } diff --git a/const_typed_builder_derive/src/info/struct_info.rs b/const_typed_builder_derive/src/parser/container.rs similarity index 63% rename from const_typed_builder_derive/src/info/struct_info.rs rename to const_typed_builder_derive/src/parser/container.rs index f8f638d..98662f0 100644 --- a/const_typed_builder_derive/src/info/struct_info.rs +++ b/const_typed_builder_derive/src/parser/container.rs @@ -1,175 +1,52 @@ -use super::field_info::{FieldInfo, FieldSettings}; -use super::group_info::{GroupInfo, GroupType}; -use crate::symbol::{ - ASSUME_MANDATORY, AT_LEAST, AT_MOST, BRUTE_FORCE, BUILDER, COMPILER, EXACT, GROUP, SINGLE, - SOLVER, -}; -use proc_macro_error::{emit_error, emit_warning}; -use quote::format_ident; use std::collections::{BTreeSet, HashMap}; + +use proc_macro_error::{emit_error, emit_warning}; use syn::Token; -/// A type alias for a collection of `FieldInfo` instances. -type FieldInfos<'a> = Vec>; +use crate::{ + info::{GroupInfo, GroupType}, + solver_kind::SolverKind, + symbol, +}; -#[derive(Debug, Clone, Copy)] -pub enum SolveType { - BruteForce, - Compiler, -} -/// Represents the information about a struct used for code generation. +/// Represents the parser for struct 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, - _mandatory_indices: BTreeSet, +pub struct Container { + assume_mandatory: bool, /// A map of group names to their respective `GroupInfo`. groups: HashMap, - /// A collection of `FieldInfo` instances representing struct fields. - field_infos: FieldInfos<'a>, + /// The indices of the mandatory fields + mandatory_indices: BTreeSet, /// The solver used to find all possible valid combinations for the groups - solve_type: SolveType, + solver_kind: SolverKind, } -impl<'a> StructInfo<'a> { - /// Creates a new `StructInfo` instance from a `syn::DeriveInput`. - /// - /// # Arguments - /// - /// - `ast`: A `syn::DeriveInput` representing the input struct. - /// - /// # Returns - /// - /// An optional `StructInfo` instance if successful, - 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 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, - }; - Some(info) - } - _ => { - emit_error!(ast, "Builder is only supported for named structs",); - None - } +impl Default for Container { + fn default() -> Self { + Container { + assume_mandatory: false, + groups: HashMap::new(), + mandatory_indices: BTreeSet::new(), + solver_kind: SolverKind::BruteForce, } } +} - /// 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 +impl Container { + pub fn assume_mandatory(&self) -> bool { + self.assume_mandatory } - /// Retrieves a reference to the map of group names to their respective `GroupInfo`. pub fn groups(&self) -> &HashMap { &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 - } -} - -/// 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 indices of the mandatory fields - mandatory_indices: BTreeSet, - /// The solver used to find all possible valid combinations for the groups - solver_type: SolveType, -} - -impl Default for StructSettings { - fn default() -> Self { - StructSettings { - builder_suffix: "Builder".to_string(), - data_suffix: "Data".to_string(), - default_field_settings: FieldSettings::new(), - groups: HashMap::new(), - mandatory_indices: BTreeSet::new(), - solver_type: SolveType::BruteForce, - } + pub fn mandatory_indices(&self) -> &BTreeSet { + &self.mandatory_indices } -} -impl StructSettings { - /// Creates a new `StructSettings` instance with default values. - fn new() -> Self { - StructSettings::default() + pub fn solver_kind(&self) -> SolverKind { + self.solver_kind } /// Add a field index to the set of mandatory indices @@ -182,11 +59,6 @@ impl StructSettings { self.groups.get_mut(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 @@ -232,8 +104,8 @@ impl StructSettings { ), } match (&attr_ident.to_string()).into() { - GROUP => self.handle_group_attribute(attr), - BUILDER => self.handle_builder_attribute(attr), + symbol::GROUP => self.handle_group_attribute(attr), + symbol::BUILDER => self.handle_builder_attribute(attr), _ => emit_error!(&attr, "Unknown attribute"), } } @@ -261,7 +133,7 @@ impl StructSettings { Err(err) => { emit_error!( &attr.meta, "Specifier cannot be parsed"; - help = "Try specifying it like #[{}(specifier)]", BUILDER; + help = "Try specifying it like #[{}(specifier)]", symbol::BUILDER; note = err ); return Ok(()); @@ -269,28 +141,19 @@ impl StructSettings { }; match (&path_ident.to_string()).into() { - ASSUME_MANDATORY => { - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - 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; - } + symbol::ASSUME_MANDATORY => { + self.assume_mandatory = true; } - SOLVER => { + symbol::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, + symbol::BRUTE_FORCE => { + self.solver_kind = SolverKind::BruteForce + } + symbol::COMPILER => self.solver_kind = SolverKind::Compiler, _ => emit_error!(&path, "Unknown solver type"), } } else { @@ -340,7 +203,7 @@ impl StructSettings { Err(err) => { emit_error!( &meta.path , "Group name is not specified correctly"; - help = "Try to define it like `#[{}(foo = {}(1))]`", GROUP, AT_LEAST; + help = "Try to define it like `#[{}(foo = {}(1))]`", symbol::GROUP, symbol::AT_LEAST; note = err ); return Ok(()); @@ -355,7 +218,7 @@ impl StructSettings { Err(err) => { emit_error!( &meta.path , "Group type is not specified correctly"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST; + help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST; note = err ); return Ok(()); @@ -364,7 +227,7 @@ impl StructSettings { _ => { emit_error!( &attr.meta, "No group type specified"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST + help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST ); return Ok(()); } @@ -376,21 +239,21 @@ impl StructSettings { 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 => { + symbol::EXACT => GroupType::Exact(group_args), + symbol::AT_LEAST => GroupType::AtLeast(group_args), + symbol::AT_MOST => GroupType::AtMost(group_args), + symbol::SINGLE => { emit_error!( args, - "`{}` doesn't take any arguments", SINGLE; - help = "`{}` is shorthand for {}(1)", SINGLE, EXACT + "`{}` doesn't take any arguments", symbol::SINGLE; + help = "`{}` is shorthand for {}(1)", symbol::SINGLE, symbol::EXACT ); return Ok(()); } _ => { emit_error!( group_type, "Unknown group type"; - help = "Known group types are {}, {} and {}", EXACT, AT_LEAST, AT_MOST + help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST ); return Ok(()); } @@ -416,14 +279,14 @@ impl StructSettings { Err(err) => { emit_error!( &meta.path , "Group type is not specified correctly"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST; + help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST; note = err ); return Ok(()); } }; match (&group_type.to_string()).into() { - EXACT | AT_LEAST | AT_MOST => { + symbol::EXACT | symbol::AT_LEAST | symbol::AT_MOST => { emit_error!( &attr.meta, "Missing arguments for group type"; @@ -431,12 +294,12 @@ impl StructSettings { ); return Ok(()); } - SINGLE => GroupType::Exact(1), + symbol::SINGLE => GroupType::Exact(1), _ => { emit_error!( group_type, "Unknown group type"; - help = "Known group types are {}, {} and {}", EXACT, AT_LEAST, AT_MOST + help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST ); return Ok(()); } @@ -445,7 +308,7 @@ impl StructSettings { _ => { emit_error!( &attr.meta, "No group type specified"; - hint = "Try to define it like `#[group({} = {}(1))]`", group_name, AT_LEAST + hint = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST ); return Ok(()); } diff --git a/const_typed_builder_derive/src/parser/field.rs b/const_typed_builder_derive/src/parser/field.rs new file mode 100644 index 0000000..13e7aa9 --- /dev/null +++ b/const_typed_builder_derive/src/parser/field.rs @@ -0,0 +1,249 @@ +use proc_macro_error::{emit_error, emit_warning}; +use std::collections::HashSet; +use syn::Token; + +use crate::{field_kind::FieldKind, symbol, util::is_option}; + +/// Represents settings for struct field generation. +#[derive(Debug, Clone)] +pub struct Field { + kind: Option, + /// Indicates if the field should propagate values. + propagate: bool, + /// The groups this field belongs to. + groups: HashSet, +} + +impl Default for Field { + fn default() -> Field { + Field { + kind: None, + propagate: false, + groups: HashSet::new(), + } + } +} + +impl Field { + pub fn kind(&self) -> Option { + self.kind + } + + pub fn groups(&self) -> &HashSet { + &self.groups + } + + pub fn propagate(&self) -> bool { + self.propagate + } + + /// Creates a new `FieldSettings` instance with default values. + pub fn handle_field(&self, field: &syn::Field, assume_mandatory: bool) -> Field { + let syn::Field { ty, attrs, .. } = field; + let mut result = self.clone(); + if result.kind == None && !is_option(ty) { + result.kind = Some(FieldKind::Mandatory); + } + attrs + .iter() + .for_each(|attr: &syn::Attribute| result.handle_attribute(attr)); + if result.kind == None { + if assume_mandatory { + result.kind = Some(FieldKind::Mandatory) + } else { + result.kind = Some(FieldKind::Optional) + } + } + result + } + + /// 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. + fn handle_attribute(&mut self, attr: &syn::Attribute) { + let attr_ident = match attr.path().require_ident() { + Ok(ident) if ident == symbol::BUILDER => ident, + Ok(ident) => { + emit_error!(ident, format!("{ident} can't be used as a field attribute")); + return; + } + Err(err) => { + emit_error!( + attr.path(), "Can't parse attribute"; + note = err + ); + return; + } + }; + + match attr.meta.require_list() { + Ok(list) => { + if list.tokens.is_empty() { + emit_warning!(list, "Empty atrribute list"); + } + } + 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, "Specifier cannot be parsed"; + help = "Try specifying it like #[{}(specifier)]", attr_ident; + note = err + ); + return Ok(()); + } + }; + + match (&path_ident.to_string()).into() { + symbol::SKIP => { + match self.kind { + None => self.kind = Some(FieldKind::Skipped), + Some(FieldKind::Optional) => emit_error!( + path_ident, "Can't define field as skipped as its already defined as optional"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Skipped) => emit_warning!(path_ident, "Defined field as skipped multiple times"), + Some(FieldKind::Mandatory) => emit_error!( + path_ident, "Can't define field as skipped as its already defined as mandatory"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Grouped) => emit_error!( + path_ident, "Can't define field as skipped when its also part of a group"; + hint = "Remove either types of attribute from this field" + ), + } + } + symbol::MANDATORY => { + match self.kind { + None => self.kind = Some(FieldKind::Mandatory), + Some(FieldKind::Optional) => emit_error!( + path_ident, "Can't define field as mandatory as its already defined as optional"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Skipped) => emit_error!( + path_ident, "Can't define field as mandatory as its already defined as skipped"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Mandatory) => emit_warning!(path_ident, "Defined field as mandatory multiple times"), + Some(FieldKind::Grouped) => emit_error!( + path_ident, "Can't define field as mandatory when its also part of a group"; + hint = "Remove either types of attribute from this field" + ), + } + } + symbol::OPTIONAL => { + match self.kind { + None => self.kind = Some(FieldKind::Optional), + Some(FieldKind::Optional) => emit_warning!(path_ident, "Defined field as optional multiple times"), + Some(FieldKind::Skipped) => emit_error!( + path_ident, "Can't define field as optional as its already defined as skipped"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Mandatory) => emit_error!( + path_ident, "Can't define field as optional as its already defined as mandatory"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Grouped) => emit_error!( + path_ident, "Can't define field as optional when its also part of a group"; + hint = "Remove either types of attribute from this field" + ), + } + } + symbol::GROUP => { + match self.kind { + None => self.kind = Some(FieldKind::Grouped), + Some(FieldKind::Optional) => emit_error!( + path_ident, "Can't define field as part of a group as its already defined as optional"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Skipped) => emit_error!( + path_ident, "Can't define field as as part of a group as its already defined as skipped"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Mandatory) => emit_error!( + path_ident, "Can't define field as as part of a group as its already defined as mandatory"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Grouped) => {}, + } + if meta.input.peek(Token![=]) { + let expr: syn::Expr = meta.value()?.parse()?; + let group_name = match expr { + syn::Expr::Path(syn::ExprPath { path, .. }) => { + match path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!( + path, "Group name not specified correctly"; + help = "Try defining it like #[{}(foo)]", symbol::BUILDER; + note = err + ); + return Ok(()); + } + }.clone() + } + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }) => { + syn::Ident::new(lit.value().as_str(), lit.span()) + } + expr => { + 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"; + help = self.groups.get(&group_name).unwrap().span() => "Remove this attribute" + ); + } else { + self.groups.insert(group_name); + } + } + } + symbol::PROPAGATE => { + self.propagate = true; + } + _ => { + emit_error!(&attr.meta, "Unknown attribute") + } + } + Ok(()) + }) + .unwrap_or_else(|err| emit_error!( + &attr.meta, "Unknown error"; + note = err + )) + } +} diff --git a/const_typed_builder_derive/src/parser/mod.rs b/const_typed_builder_derive/src/parser/mod.rs new file mode 100644 index 0000000..02ef743 --- /dev/null +++ b/const_typed_builder_derive/src/parser/mod.rs @@ -0,0 +1,5 @@ +mod container; +mod field; + +pub use container::Container; +pub use field::Field; diff --git a/const_typed_builder_derive/src/solver_kind.rs b/const_typed_builder_derive/src/solver_kind.rs new file mode 100644 index 0000000..d2b62a9 --- /dev/null +++ b/const_typed_builder_derive/src/solver_kind.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone, Copy)] +pub enum SolverKind { + BruteForce, + Compiler, +} diff --git a/const_typed_builder_derive/src/util.rs b/const_typed_builder_derive/src/util.rs new file mode 100644 index 0000000..33aeae2 --- /dev/null +++ b/const_typed_builder_derive/src/util.rs @@ -0,0 +1,35 @@ +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_test/compile_fail/group_and_mandatory_3.stderr b/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr deleted file mode 100644 index 6f7697c..0000000 --- a/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr +++ /dev/null @@ -1,21 +0,0 @@ -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:14:18 - | -6 | pub struct Foo { - | -------------- function or associated item `builder` not found for this struct -... -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 - = 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 deleted file mode 100644 index c708d76..0000000 --- a/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr +++ /dev/null @@ -1,21 +0,0 @@ -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)] - | ^^^^^^^^^ - -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` diff --git a/const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs b/const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs index a02bdfa..5ae2524 100644 --- a/const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs +++ b/const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs @@ -3,7 +3,7 @@ use const_typed_builder::Builder; fn main() { #[derive(Debug, Default, PartialEq, Eq, Builder)] pub struct Foo { - #[builder(mandatory = true)] + #[builder(mandatory)] bar: Option, } diff --git a/const_typed_builder_test/src/lib.rs b/const_typed_builder_test/src/lib.rs index 4d33e8c..7198b49 100644 --- a/const_typed_builder_test/src/lib.rs +++ b/const_typed_builder_test/src/lib.rs @@ -188,7 +188,7 @@ mod test { fn optional_mandatory_set() { #[derive(Debug, Default, PartialEq, Eq, Builder)] pub struct Foo { - #[builder(mandatory = true)] + #[builder(mandatory)] bar: Option, } From af989c1a6170694f300032f065dbf7575f1e9baf Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Sat, 7 Oct 2023 19:11:41 +0200 Subject: [PATCH 02/15] Refactor parse and info --- .../src/generator/field_generator.rs | 9 +- .../src/generator/generics_generator.rs | 9 +- .../src/generator/group_generator.rs | 6 +- .../src/info/container.rs | 80 ++--- const_typed_builder_derive/src/info/field.rs | 77 +---- .../src/info/{group_info.rs => group.rs} | 20 +- const_typed_builder_derive/src/info/mod.rs | 6 +- const_typed_builder_derive/src/lib.rs | 5 +- .../src/parser/container.rs | 268 ++++---------- .../src/parser/field.rs | 326 ++++++++++-------- .../src/parser/group.rs | 136 ++++++++ const_typed_builder_derive/src/parser/mod.rs | 1 + .../compile_fail/group_and_mandatory_3.stderr | 21 ++ .../compile_fail/group_and_mandatory_4.stderr | 21 ++ 14 files changed, 503 insertions(+), 482 deletions(-) rename const_typed_builder_derive/src/info/{group_info.rs => group.rs} (96%) create mode 100644 const_typed_builder_derive/src/parser/group.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.stderr diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index ebf3f71..524b49b 100644 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ b/const_typed_builder_derive/src/generator/field_generator.rs @@ -1,11 +1,14 @@ -use crate::{field_kind::FieldKind, info::Field}; +use crate::{ + field_kind::FieldKind, + info::{Field, FieldCollection}, +}; 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 [Field<'a>], + fields: &'a FieldCollection<'a>, } impl<'a> FieldGenerator<'a> { @@ -18,7 +21,7 @@ impl<'a> FieldGenerator<'a> { /// # Returns /// /// A `FieldGenerator` instance initialized with the provided fields. - pub fn new(fields: &'a [Field]) -> Self { + pub fn new(fields: &'a FieldCollection<'a>) -> Self { Self { fields } } diff --git a/const_typed_builder_derive/src/generator/generics_generator.rs b/const_typed_builder_derive/src/generator/generics_generator.rs index 106166f..924aa2c 100644 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ b/const_typed_builder_derive/src/generator/generics_generator.rs @@ -1,4 +1,7 @@ -use crate::{field_kind::FieldKind, info::Field}; +use crate::{ + field_kind::FieldKind, + info::{Field, FieldCollection}, +}; use either::Either; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; @@ -7,7 +10,7 @@ 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 [Field<'a>], + pub fields: &'a FieldCollection<'a>, target_generics: &'a syn::Generics, } @@ -22,7 +25,7 @@ pub(super) struct GenericsGenerator<'a> { /// /// A `GenericsGenerator` instance initialized with the provided fields and target generics. impl<'a> GenericsGenerator<'a> { - pub fn new(fields: &'a [Field], target_generics: &'a syn::Generics) -> Self { + pub fn new(fields: &'a FieldCollection<'a>, target_generics: &'a syn::Generics) -> Self { Self { fields, target_generics, diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs index edd662e..6d37b5e 100644 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ b/const_typed_builder_derive/src/generator/group_generator.rs @@ -1,5 +1,5 @@ use crate::{ - info::{GroupInfo, GroupType}, + info::{Group, GroupType}, CONST_IDENT_PREFIX, }; use itertools::{Itertools, Powerset}; @@ -10,7 +10,7 @@ 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)] pub(super) struct GroupGenerator<'a> { - groups: Vec<&'a GroupInfo>, + groups: Vec<&'a Group>, } impl<'a> GroupGenerator<'a> { @@ -23,7 +23,7 @@ impl<'a> GroupGenerator<'a> { /// # Returns /// /// A `GroupGenerator` instance initialized with the provided groups. - pub fn new(groups: Vec<&'a GroupInfo>) -> Self { + pub fn new(groups: Vec<&'a Group>) -> Self { groups.iter().for_each(|group| group.check()); Self { groups } } diff --git a/const_typed_builder_derive/src/info/container.rs b/const_typed_builder_derive/src/info/container.rs index b75a06c..298d767 100644 --- a/const_typed_builder_derive/src/info/container.rs +++ b/const_typed_builder_derive/src/info/container.rs @@ -1,13 +1,8 @@ -use super::field::Field; -use super::group_info::GroupInfo; -use crate::{parser, solver_kind::SolverKind}; +use super::field::FieldCollection; +use super::group::GroupCollection; +use crate::solver_kind::SolverKind; -use proc_macro_error::emit_error; use quote::format_ident; -use std::collections::{BTreeSet, HashMap}; - -/// A type alias for a collection of `FieldInfo` instances. -type FieldCollection<'a> = Vec>; /// Represents the information about a struct used for code generation. #[derive(Debug)] @@ -22,13 +17,12 @@ pub struct Container<'a> { builder_ident: syn::Ident, /// The identifier of the generated data struct. data_ident: syn::Ident, - _mandatory_indices: BTreeSet, /// A map of group names to their respective `GroupInfo`. - groups: HashMap, + groups: GroupCollection, /// A collection of `FieldInfo` instances representing struct fields. field_collection: FieldCollection<'a>, /// The solver used to find all possible valid combinations for the groups - solve_type: SolverKind, + solver_kind: SolverKind, } impl<'a> Container<'a> { @@ -41,49 +35,23 @@ impl<'a> Container<'a> { /// # Returns /// /// An optional `StructInfo` instance if successful, - 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 = parser::Container::default().with_attrs(attrs); - - let field_infos = fields - .named - .iter() - .enumerate() - .map(|(index, field)| Field::new(field, &mut settings, index)) - .collect::>>()?; - - let info = Container { - ident, - vis, - generics, - builder_ident: format_ident!("{}{}", ident, "Builder"), - data_ident: format_ident!("{}{}", ident, "Data"), - _mandatory_indices: settings.mandatory_indices().clone(), - groups: settings.groups().clone(), - field_collection: field_infos, - solve_type: settings.solver_kind(), - }; - Some(info) - } - _ => { - emit_error!(ast, "Builder is only supported for named structs",); - None - } + pub fn new( + vis: &'a syn::Visibility, + generics: &'a syn::Generics, + ident: &'a syn::Ident, + group_collection: GroupCollection, + field_collection: FieldCollection<'a>, + solver_kind: SolverKind, + ) -> Self { + Container { + ident, + vis, + generics, + builder_ident: format_ident!("{}{}", ident, "Builder"), + data_ident: format_ident!("{}{}", ident, "Data"), + groups: group_collection, + field_collection, + solver_kind, } } @@ -118,12 +86,12 @@ impl<'a> Container<'a> { } /// Retrieves a reference to the map of group names to their respective `GroupInfo`. - pub fn groups(&self) -> &HashMap { + pub fn groups(&self) -> &GroupCollection { &self.groups } /// Retrieves the solver type used to find all possible valid combinations for the groups pub fn solve_type(&self) -> SolverKind { - self.solve_type + self.solver_kind } } diff --git a/const_typed_builder_derive/src/info/field.rs b/const_typed_builder_derive/src/info/field.rs index 1657d83..57515c0 100644 --- a/const_typed_builder_derive/src/info/field.rs +++ b/const_typed_builder_derive/src/info/field.rs @@ -1,35 +1,23 @@ use crate::{ field_kind::FieldKind, - parser, symbol, util::{inner_type, is_option}, CONST_IDENT_PREFIX, }; -use proc_macro_error::emit_error; use quote::format_ident; +/// A type alias for a collection of `FieldInfo` instances. +pub type FieldCollection<'a> = Vec>; + /// Represents the information about a struct field used for code generation. #[derive(Debug, PartialEq, Eq)] pub struct Field<'a> { - field: &'a syn::Field, + ty: &'a syn::Type, ident: &'a syn::Ident, index: usize, propagate: bool, 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, -// /// Indicates a field that not included in the builder. -// Skipped, -// } - impl<'a> Field<'a> { /// Creates a new `FieldInfo` instance for a struct field. /// @@ -43,47 +31,18 @@ impl<'a> Field<'a> { /// /// An otpional `FieldInfo` instance if successful. pub fn new( - field: &'a syn::Field, - struct_parser: &mut parser::Container, + ident: &'a syn::Ident, + ty: &'a syn::Type, index: usize, - ) -> Option { - if let syn::Field { - ident: Some(ident), .. - } = field - { - let field_parser = - parser::Field::default().handle_field(field, struct_parser.assume_mandatory()); - let kind = field_parser - .kind() - .expect("Kind should always be set after parsing"); - - match kind { - FieldKind::Optional | FieldKind::Skipped => {}, - FieldKind::Mandatory => assert!(struct_parser.add_mandatory_index(index)), - FieldKind::Grouped => field_parser.groups().iter().for_each(|group_name| { - if let Some(group) = struct_parser.group_by_name_mut(&group_name.to_string()) - { - group.associate(index); - } else { - emit_error!( - group_name, - "No group called {} is available", group_name; - hint = "You might want to add a #[{}(...)] attribute to the container", symbol::GROUP - ); - } - }), - }; - - Some(Self { - field, - ident, - index, - propagate: field_parser.propagate(), - kind, - }) - } else { - emit_error!(field, "Unnamed fields are not supported"); - None + kind: FieldKind, + propagate: bool, + ) -> Self { + Self { + ident, + index, + ty, + propagate, + kind, } } @@ -99,17 +58,17 @@ impl<'a> Field<'a> { /// Checks if the field's type is an Option. pub fn is_option_type(&self) -> bool { - is_option(&self.field.ty) + is_option(self.ty) } /// Retrieves the type of the field. pub fn ty(&self) -> &syn::Type { - &self.field.ty + self.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) + inner_type(self.ty) } /// Retrieves the kind of the field, which can be Optional, Mandatory, or Grouped. diff --git a/const_typed_builder_derive/src/info/group_info.rs b/const_typed_builder_derive/src/info/group.rs similarity index 96% rename from const_typed_builder_derive/src/info/group_info.rs rename to const_typed_builder_derive/src/info/group.rs index 43242fa..d73773e 100644 --- a/const_typed_builder_derive/src/info/group_info.rs +++ b/const_typed_builder_derive/src/info/group.rs @@ -1,17 +1,23 @@ use proc_macro_error::{emit_error, emit_warning}; use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; -use std::{cmp::Ordering, collections::BTreeSet, hash::Hash}; +use std::{ + cmp::Ordering, + collections::{BTreeSet, HashMap}, + hash::Hash, +}; + +pub type GroupCollection = HashMap; /// Represents information about a group, including its name, member count, and group type. #[derive(Debug, Clone)] -pub struct GroupInfo { +pub struct Group { name: syn::Ident, associated_indices: BTreeSet, group_type: GroupType, } -impl GroupInfo { +impl Group { /// Creates a new `GroupInfo` instance. /// /// # Arguments @@ -23,7 +29,7 @@ impl GroupInfo { /// /// A `GroupInfo` instance with the provided name and group type. pub fn new(name: syn::Ident, group_type: GroupType) -> Self { - GroupInfo { + Group { name, associated_indices: BTreeSet::new(), group_type, @@ -160,15 +166,15 @@ impl GroupInfo { } } -impl Eq for GroupInfo {} +impl Eq for Group {} -impl PartialEq for GroupInfo { +impl PartialEq for Group { fn eq(&self, other: &Self) -> bool { self.name == other.name } } -impl Hash for GroupInfo { +impl Hash for Group { fn hash(&self, state: &mut H) { self.name.to_string().hash(state); } diff --git a/const_typed_builder_derive/src/info/mod.rs b/const_typed_builder_derive/src/info/mod.rs index fd234ec..e57f2c0 100644 --- a/const_typed_builder_derive/src/info/mod.rs +++ b/const_typed_builder_derive/src/info/mod.rs @@ -1,7 +1,7 @@ mod container; mod field; -mod group_info; +mod group; pub use container::Container; -pub use field::Field; -pub use group_info::{GroupInfo, GroupType}; +pub use field::{Field, FieldCollection}; +pub use group::{Group, GroupCollection, GroupType}; diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index 21f7a91..7c22132 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -7,7 +7,6 @@ mod symbol; mod util; use generator::Generator; -use info::Container; use proc_macro2::TokenStream; use proc_macro_error::proc_macro_error; use quote::quote; @@ -54,7 +53,7 @@ pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// /// An optional `TokenStream` representing the generated token stream. fn impl_my_derive(ast: &syn::DeriveInput) -> Option { - let struct_info = Container::new(ast)?; - let generator = Generator::new(&struct_info); + let container_info = &parser::Container::parse(ast)?; + let generator = Generator::new(container_info); Some(generator.generate()) } diff --git a/const_typed_builder_derive/src/parser/container.rs b/const_typed_builder_derive/src/parser/container.rs index 98662f0..3f1669a 100644 --- a/const_typed_builder_derive/src/parser/container.rs +++ b/const_typed_builder_derive/src/parser/container.rs @@ -1,64 +1,23 @@ -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use proc_macro_error::{emit_error, emit_warning}; use syn::Token; -use crate::{ - info::{GroupInfo, GroupType}, - solver_kind::SolverKind, - symbol, -}; +use crate::{info, solver_kind::SolverKind, symbol}; + +use super::{group::Group, Field}; /// Represents the parser for struct generation. #[derive(Debug)] pub struct Container { assume_mandatory: bool, /// A map of group names to their respective `GroupInfo`. - groups: HashMap, - /// The indices of the mandatory fields - mandatory_indices: BTreeSet, + groups: info::GroupCollection, /// The solver used to find all possible valid combinations for the groups solver_kind: SolverKind, } -impl Default for Container { - fn default() -> Self { - Container { - assume_mandatory: false, - groups: HashMap::new(), - mandatory_indices: BTreeSet::new(), - solver_kind: SolverKind::BruteForce, - } - } -} - impl Container { - pub fn assume_mandatory(&self) -> bool { - self.assume_mandatory - } - - pub fn groups(&self) -> &HashMap { - &self.groups - } - - pub fn mandatory_indices(&self) -> &BTreeSet { - &self.mandatory_indices - } - - pub fn solver_kind(&self) -> SolverKind { - self.solver_kind - } - - /// 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) - } - /// Updates struct settings based on provided attributes. /// /// # Arguments @@ -68,9 +27,68 @@ impl Container { /// # Returns /// /// A `syn::Result` indicating success or failure of attribute handling. - pub fn with_attrs(mut self, attrs: &[syn::Attribute]) -> Self { - attrs.iter().for_each(|attr| self.handle_attribute(attr)); - self + pub fn parse(ast: &syn::DeriveInput) -> Option { + match &ast { + syn::DeriveInput { + attrs, + vis, + ident, + generics, + data: + syn::Data::Struct(syn::DataStruct { + fields: syn::Fields::Named(fields), + .. + }), + } => { + let mut result = Container { + assume_mandatory: false, + groups: HashMap::new(), + solver_kind: SolverKind::BruteForce, + }; + + attrs.iter().for_each(|attr| result.handle_attribute(attr)); + + let fields = fields + .named + .iter() + .enumerate() + .map(|(index, field)| { + assert!(field.ident.is_some()); + Field::parse( + field + .ident + .as_ref() + .expect("FieldsNamed should have a named field"), + field, + &mut result, + index, + ) + }) + .collect::>(); + + Some(info::Container::new( + vis, + generics, + ident, + result.groups, + fields, + result.solver_kind, + )) + } + _ => { + emit_error!(ast, "Builder is only supported for named structs",); + None + } + } + } + + pub fn assume_mandatory(&self) -> bool { + self.assume_mandatory + } + + /// Get a GroupInfo by its identifier + pub fn group_by_name_mut(&mut self, group_name: &String) -> Option<&mut info::Group> { + self.groups.get_mut(group_name) } /// Handles the parsing and processing of attributes applied to a struct. @@ -104,7 +122,11 @@ impl Container { ), } match (&attr_ident.to_string()).into() { - symbol::GROUP => self.handle_group_attribute(attr), + symbol::GROUP => { + if let Some(group) = Group::parse(attr) { + self.groups.insert(group.name().to_string(), group); + } + } symbol::BUILDER => self.handle_builder_attribute(attr), _ => emit_error!(&attr, "Unknown attribute"), } @@ -179,150 +201,4 @@ impl Container { ) }) } - - /// 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. - fn handle_group_attribute(&mut self, attr: &syn::Attribute) { - attr.parse_nested_meta(|meta| { - let group_name = match meta.path.require_ident() { - Ok(ident) => ident, - Err(err) => { - emit_error!( - &meta.path , "Group name is not specified correctly"; - help = "Try to define it like `#[{}(foo = {}(1))]`", symbol::GROUP, symbol::AT_LEAST; - note = err - ); - return Ok(()); - } - }; - - let group_type = match meta.value()?.parse()? { - syn::Expr::Call(syn::ExprCall { func, args, .. }) => { - let group_type = match func.as_ref() { - syn::Expr::Path(syn::ExprPath { path, .. }) => match path.require_ident() { - Ok(ident) => ident, - Err(err) => { - emit_error!( - &meta.path , "Group type is not specified correctly"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST; - note = err - ); - return Ok(()); - } - }, - _ => { - emit_error!( - &attr.meta, "No group type specified"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST - ); - return Ok(()); - } - }; - - 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() { - symbol::EXACT => GroupType::Exact(group_args), - symbol::AT_LEAST => GroupType::AtLeast(group_args), - symbol::AT_MOST => GroupType::AtMost(group_args), - symbol::SINGLE => { - emit_error!( - args, - "`{}` doesn't take any arguments", symbol::SINGLE; - help = "`{}` is shorthand for {}(1)", symbol::SINGLE, symbol::EXACT - ); - return Ok(()); - } - _ => { - emit_error!( - group_type, "Unknown group type"; - help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST - ); - return Ok(()); - } - }, - Err(err) => { - emit_error!( - val, "Couldn't parse group argument"; - note = err - ); - return Ok(()); - } - }, - - _ => { - emit_error!(func, "Can't parse group argument"); - return Ok(()); - } - } - } - syn::Expr::Path(syn::ExprPath { path, .. }) => { - let group_type = match path.require_ident() { - Ok(ident) => ident, - Err(err) => { - emit_error!( - &meta.path , "Group type is not specified correctly"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST; - note = err - ); - return Ok(()); - } - }; - match (&group_type.to_string()).into() { - symbol::EXACT | symbol::AT_LEAST | symbol::AT_MOST => { - emit_error!( - &attr.meta, - "Missing arguments for group type"; - help = "Try `{}(1)`, or any other usize", &group_type - ); - return Ok(()); - } - symbol::SINGLE => GroupType::Exact(1), - _ => { - emit_error!( - group_type, - "Unknown group type"; - help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST - ); - return Ok(()); - } - } - } - _ => { - emit_error!( - &attr.meta, "No group type specified"; - hint = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST - ); - return Ok(()); - } - }; - - self.groups.insert( - group_name.to_string(), - GroupInfo::new(group_name.clone(), group_type), - ); - Ok(()) - }) - .unwrap_or_else(|err| emit_error!( - &attr.meta, "Unknown error"; - note = err - )) - } } diff --git a/const_typed_builder_derive/src/parser/field.rs b/const_typed_builder_derive/src/parser/field.rs index 13e7aa9..b824244 100644 --- a/const_typed_builder_derive/src/parser/field.rs +++ b/const_typed_builder_derive/src/parser/field.rs @@ -1,8 +1,9 @@ +use crate::{field_kind::FieldKind, symbol, util::is_option}; use proc_macro_error::{emit_error, emit_warning}; use std::collections::HashSet; -use syn::Token; -use crate::{field_kind::FieldKind, symbol, util::is_option}; +use super::Container; +use crate::info; /// Represents settings for struct field generation. #[derive(Debug, Clone)] @@ -14,47 +15,65 @@ pub struct Field { groups: HashSet, } -impl Default for Field { - fn default() -> Field { - Field { - kind: None, - propagate: false, - groups: HashSet::new(), - } - } -} +// impl Default for Field { +// fn default() -> Field { +// Field { +// index: 0, +// ident: syn::Ident::new("", ) +// kind: None, +// propagate: false, +// groups: HashSet::new(), +// } +// } +// } impl Field { - pub fn kind(&self) -> Option { - self.kind - } - - pub fn groups(&self) -> &HashSet { - &self.groups - } - - pub fn propagate(&self) -> bool { - self.propagate - } - /// Creates a new `FieldSettings` instance with default values. - pub fn handle_field(&self, field: &syn::Field, assume_mandatory: bool) -> Field { + pub fn parse<'a>( + ident: &'a syn::Ident, + field: &'a syn::Field, + container_parser: &mut Container, + index: usize, + ) -> info::Field<'a> { + // let mut result = Self::default(); let syn::Field { ty, attrs, .. } = field; - let mut result = self.clone(); - if result.kind == None && !is_option(ty) { - result.kind = Some(FieldKind::Mandatory); + + let mut result = Self { + kind: None, + propagate: false, + groups: HashSet::new(), + }; + + if result.kind.is_none() && !is_option(ty) { + result.kind = Some(FieldKind::Mandatory); // If its not an option type it MUST always be mandatory } + attrs .iter() .for_each(|attr: &syn::Attribute| result.handle_attribute(attr)); - if result.kind == None { - if assume_mandatory { + + match result.kind { + Some(FieldKind::Grouped) => result.groups.iter().for_each(|group_name| { + if let Some(group) = container_parser.group_by_name_mut(&group_name.to_string()) + { + group.associate(index); + } else { + emit_error!( + group_name, + "No group called {} is available", group_name; + hint = "You might want to add a #[{}(...)] attribute to the container", symbol::GROUP + ); + } + }), + Some(_) => {} + None => if container_parser.assume_mandatory() { result.kind = Some(FieldKind::Mandatory) } else { result.kind = Some(FieldKind::Optional) - } + }, } - result + + info::Field::new(ident, ty, index, result.kind.unwrap(), result.propagate) } /// Handles the parsing and processing of a builder attribute applied to a field. @@ -81,6 +100,7 @@ impl Field { /// # Arguments /// /// - `attr`: A reference to the `syn::Attribute` representing the builder attribute applied to the field. + /// fn handle_attribute(&mut self, attr: &syn::Attribute) { let attr_ident = match attr.path().require_ident() { Ok(ident) if ident == symbol::BUILDER => ident, @@ -124,126 +144,134 @@ impl Field { }; match (&path_ident.to_string()).into() { - symbol::SKIP => { - match self.kind { - None => self.kind = Some(FieldKind::Skipped), - Some(FieldKind::Optional) => emit_error!( - path_ident, "Can't define field as skipped as its already defined as optional"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Skipped) => emit_warning!(path_ident, "Defined field as skipped multiple times"), - Some(FieldKind::Mandatory) => emit_error!( - path_ident, "Can't define field as skipped as its already defined as mandatory"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Grouped) => emit_error!( - path_ident, "Can't define field as skipped when its also part of a group"; - hint = "Remove either types of attribute from this field" - ), - } - } - symbol::MANDATORY => { - match self.kind { - None => self.kind = Some(FieldKind::Mandatory), - Some(FieldKind::Optional) => emit_error!( - path_ident, "Can't define field as mandatory as its already defined as optional"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Skipped) => emit_error!( - path_ident, "Can't define field as mandatory as its already defined as skipped"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Mandatory) => emit_warning!(path_ident, "Defined field as mandatory multiple times"), - Some(FieldKind::Grouped) => emit_error!( - path_ident, "Can't define field as mandatory when its also part of a group"; - hint = "Remove either types of attribute from this field" - ), - } - } - symbol::OPTIONAL => { - match self.kind { - None => self.kind = Some(FieldKind::Optional), - Some(FieldKind::Optional) => emit_warning!(path_ident, "Defined field as optional multiple times"), - Some(FieldKind::Skipped) => emit_error!( - path_ident, "Can't define field as optional as its already defined as skipped"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Mandatory) => emit_error!( - path_ident, "Can't define field as optional as its already defined as mandatory"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Grouped) => emit_error!( - path_ident, "Can't define field as optional when its also part of a group"; - hint = "Remove either types of attribute from this field" - ), - } - } - symbol::GROUP => { - match self.kind { - None => self.kind = Some(FieldKind::Grouped), - Some(FieldKind::Optional) => emit_error!( - path_ident, "Can't define field as part of a group as its already defined as optional"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Skipped) => emit_error!( - path_ident, "Can't define field as as part of a group as its already defined as skipped"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Mandatory) => emit_error!( - path_ident, "Can't define field as as part of a group as its already defined as mandatory"; - hint = "Remove either types of attribute from this field" - ), - Some(FieldKind::Grouped) => {}, - } - if meta.input.peek(Token![=]) { - let expr: syn::Expr = meta.value()?.parse()?; - let group_name = match expr { - syn::Expr::Path(syn::ExprPath { path, .. }) => { - match path.require_ident() { - Ok(ident) => ident, - Err(err) => { - emit_error!( - path, "Group name not specified correctly"; - help = "Try defining it like #[{}(foo)]", symbol::BUILDER; - note = err - ); - return Ok(()); - } - }.clone() - } - syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }) => { - syn::Ident::new(lit.value().as_str(), lit.span()) - } - expr => { - 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"; - help = self.groups.get(&group_name).unwrap().span() => "Remove this attribute" - ); - } else { - self.groups.insert(group_name); - } - } - } - symbol::PROPAGATE => { - self.propagate = true; - } - _ => { - emit_error!(&attr.meta, "Unknown attribute") - } + symbol::SKIP => self.handle_skip(path_ident), + symbol::MANDATORY => self.handle_mandatory(path_ident), + symbol::OPTIONAL => self.handle_optional(path_ident), + symbol::GROUP => self.handle_group(path_ident, &meta), + symbol::PROPAGATE => self.propagate = true, + _ => emit_error!(&attr.meta, "Unknown attribute"), } 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 + ) + }) + } + + fn handle_skip(&mut self, ident: &syn::Ident) { + match self.kind { + None => self.kind = Some(FieldKind::Skipped), + Some(FieldKind::Optional) => emit_error!( + ident, "Can't define field as skipped as its already defined as optional"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Skipped) => { + emit_warning!(ident, "Defined field as skipped multiple times") + } + Some(FieldKind::Mandatory) => emit_error!( + ident, "Can't define field as skipped as its already defined as mandatory"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Grouped) => emit_error!( + ident, "Can't define field as skipped when its also part of a group"; + hint = "Remove either types of attribute from this field" + ), + } + } + + fn handle_mandatory(&mut self, ident: &syn::Ident) { + match self.kind { + None => self.kind = Some(FieldKind::Mandatory), + Some(FieldKind::Optional) => emit_error!( + ident, "Can't define field as mandatory as its already defined as optional"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Skipped) => emit_error!( + ident, "Can't define field as mandatory as its already defined as skipped"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Mandatory) => { + emit_warning!(ident, "Defined field as mandatory multiple times") + } + Some(FieldKind::Grouped) => emit_error!( + ident, "Can't define field as mandatory when its also part of a group"; + hint = "Remove either types of attribute from this field" + ), + } + } + + fn handle_optional(&mut self, ident: &syn::Ident) { + match self.kind { + None => self.kind = Some(FieldKind::Optional), + Some(FieldKind::Optional) => { + emit_warning!(ident, "Defined field as optional multiple times") + } + Some(FieldKind::Skipped) => emit_error!( + ident, "Can't define field as optional as its already defined as skipped"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Mandatory) => emit_error!( + ident, "Can't define field as optional as its already defined as mandatory"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Grouped) => emit_error!( + ident, "Can't define field as optional when its also part of a group"; + hint = "Remove either types of attribute from this field" + ), + } + } + + fn handle_group(&mut self, ident: &syn::Ident, meta: &syn::meta::ParseNestedMeta) { + match self.kind { + None => self.kind = Some(FieldKind::Grouped), + Some(FieldKind::Optional) => emit_error!( + ident, "Can't define field as part of a group as its already defined as optional"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Skipped) => emit_error!( + ident, "Can't define field as as part of a group as its already defined as skipped"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Mandatory) => emit_error!( + ident, "Can't define field as as part of a group as its already defined as mandatory"; + hint = "Remove either types of attribute from this field" + ), + Some(FieldKind::Grouped) => {} + } + match self.extract_group_name(meta) { + Ok(group_name) => { + 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); + } + } + Err(err) => { + emit_error!( + ident, "Group name not specified correctly"; + help = "Try defining it like #[{}(foo)]", symbol::BUILDER; + note = err + ); + } + }; + } + + fn extract_group_name(&self, meta: &syn::meta::ParseNestedMeta) -> syn::Result { + match meta.value()?.parse()? { + syn::Expr::Path(syn::ExprPath { path, .. }) => { + path.require_ident().map(|ident| ident.clone()) + } + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }) => Ok(syn::Ident::new(lit.value().as_str(), lit.span())), + expr => Err(syn::Error::new_spanned(expr, "Unexpected expresion type")), + } } } diff --git a/const_typed_builder_derive/src/parser/group.rs b/const_typed_builder_derive/src/parser/group.rs new file mode 100644 index 0000000..0e319ce --- /dev/null +++ b/const_typed_builder_derive/src/parser/group.rs @@ -0,0 +1,136 @@ +use proc_macro_error::emit_error; + +use crate::{info, symbol}; + +pub struct Group; + +impl Group { + pub fn parse(attr: &syn::Attribute) -> Option { + let mut group = None; + attr.parse_nested_meta(|meta| { + let group_name = match meta.path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!( + &meta.path , "Group name is not specified correctly"; + help = "Try to define it like `#[{}(foo = {}(1))]`", symbol::GROUP, symbol::AT_LEAST; + note = err + ); + return Ok(()); + } + }; + + let group_type = match meta.value()?.parse()? { + syn::Expr::Call(syn::ExprCall { func, args, .. }) => { + let group_type = match func.as_ref() { + syn::Expr::Path(syn::ExprPath { path, .. }) => match path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!( + &meta.path , "Group type is not specified correctly"; + help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST; + note = err + ); + return Ok(()); + } + }, + _ => { + emit_error!( + &attr.meta, "No group type specified"; + help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST + ); + return Ok(()); + } + }; + + 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() { + symbol::EXACT => info::GroupType::Exact(group_args), + symbol::AT_LEAST => info::GroupType::AtLeast(group_args), + symbol::AT_MOST => info::GroupType::AtMost(group_args), + symbol::SINGLE => { + emit_error!( + args, + "`{}` doesn't take any arguments", symbol::SINGLE; + help = "`{}` is shorthand for {}(1)", symbol::SINGLE, symbol::EXACT + ); + return Ok(()); + } + _ => { + emit_error!( + group_type, "Unknown group type"; + help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST + ); + return Ok(()); + } + }, + Err(err) => { + emit_error!( + val, "Couldn't parse group argument"; + note = err + ); + return Ok(()); + } + }, + + _ => { + emit_error!(func, "Can't parse group argument"); + return Ok(()); + } + } + } + syn::Expr::Path(syn::ExprPath { path, .. }) => { + let group_type = match path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!( + &meta.path , "Group type is not specified correctly"; + help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST; + note = err + ); + return Ok(()); + } + }; + match (&group_type.to_string()).into() { + symbol::EXACT | symbol::AT_LEAST | symbol::AT_MOST => { + emit_error!( + &attr.meta, + "Missing arguments for group type"; + help = "Try `{}(1)`, or any other usize", &group_type + ); + return Ok(()); + } + symbol::SINGLE => info::GroupType::Exact(1), + _ => { + emit_error!( + group_type, + "Unknown group type"; + help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST + ); + return Ok(()); + } + } + } + _ => { + emit_error!( + &attr.meta, "No group type specified"; + hint = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST + ); + return Ok(()); + } + }; + group = Some(info::Group::new(group_name.clone(), group_type)); + + Ok(()) + }) + .unwrap_or_else(|err| emit_error!( + &attr.meta, "Unknown error"; + note = err + )); + group + } +} diff --git a/const_typed_builder_derive/src/parser/mod.rs b/const_typed_builder_derive/src/parser/mod.rs index 02ef743..bc3a865 100644 --- a/const_typed_builder_derive/src/parser/mod.rs +++ b/const_typed_builder_derive/src/parser/mod.rs @@ -1,5 +1,6 @@ mod container; mod field; +mod group; pub use container::Container; pub use field::Field; 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..d7a1b13 --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_3.stderr @@ -0,0 +1,21 @@ +error: Can't define field as as part of a group as its already defined as mandatory + + = 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:14:18 + | +6 | pub struct Foo { + | -------------- function or associated item `builder` not found for this struct +... +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 + = 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 new file mode 100644 index 0000000..03bf60e --- /dev/null +++ b/const_typed_builder_test/compile_fail/group_and_mandatory_4.stderr @@ -0,0 +1,21 @@ +error: Can't define field as mandatory when its also part of a group + + = help: Remove either types of attribute from this field + + --> ./compile_fail/group_and_mandatory_4.rs:10:19 + | +10 | #[builder(mandatory)] + | ^^^^^^^^^ + +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 52dc9b95df542eeec16ee55e03fb5cfd28a38f3a Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 11:45:57 +0200 Subject: [PATCH 03/15] Another refactoring step --- const_typed_builder_derive/src/field_kind.rs | 7 - .../src/generator/builder_generator.rs | 2 +- .../src/generator/field_generator.rs | 5 +- .../src/generator/generics_generator.rs | 5 +- .../src/info/container.rs | 7 +- const_typed_builder_derive/src/info/field.rs | 9 +- const_typed_builder_derive/src/info/mod.rs | 4 +- const_typed_builder_derive/src/lib.rs | 6 +- .../src/parser/container.rs | 184 +++++++------- .../src/parser/field.rs | 173 ++++++------- .../src/parser/group.rs | 229 ++++++++++-------- const_typed_builder_derive/src/parser/mod.rs | 3 +- const_typed_builder_derive/src/solver_kind.rs | 5 - 13 files changed, 312 insertions(+), 327 deletions(-) delete mode 100644 const_typed_builder_derive/src/field_kind.rs delete mode 100644 const_typed_builder_derive/src/solver_kind.rs diff --git a/const_typed_builder_derive/src/field_kind.rs b/const_typed_builder_derive/src/field_kind.rs deleted file mode 100644 index aab5b79..0000000 --- a/const_typed_builder_derive/src/field_kind.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum FieldKind { - Optional, - Skipped, - Mandatory, - Grouped, -} diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 776b4e4..fdaf037 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::{field_kind::FieldKind, solver_kind::SolverKind}; +use crate::{info::FieldKind, info::SolverKind}; use convert_case::{Case, Casing}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs index 524b49b..b86710b 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::{ - field_kind::FieldKind, - info::{Field, FieldCollection}, -}; +use crate::info::{Field, FieldCollection, FieldKind}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; diff --git a/const_typed_builder_derive/src/generator/generics_generator.rs b/const_typed_builder_derive/src/generator/generics_generator.rs index 924aa2c..3ddb667 100644 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ b/const_typed_builder_derive/src/generator/generics_generator.rs @@ -1,7 +1,4 @@ -use crate::{ - field_kind::FieldKind, - info::{Field, FieldCollection}, -}; +use crate::info::{Field, FieldCollection, FieldKind}; use either::Either; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; diff --git a/const_typed_builder_derive/src/info/container.rs b/const_typed_builder_derive/src/info/container.rs index 298d767..6b3bf5c 100644 --- a/const_typed_builder_derive/src/info/container.rs +++ b/const_typed_builder_derive/src/info/container.rs @@ -1,9 +1,14 @@ use super::field::FieldCollection; use super::group::GroupCollection; -use crate::solver_kind::SolverKind; use quote::format_ident; +#[derive(Debug, Clone, Copy)] +pub enum SolverKind { + BruteForce, + Compiler, +} + /// Represents the information about a struct used for code generation. #[derive(Debug)] pub struct Container<'a> { diff --git a/const_typed_builder_derive/src/info/field.rs b/const_typed_builder_derive/src/info/field.rs index 57515c0..d7ae0b3 100644 --- a/const_typed_builder_derive/src/info/field.rs +++ b/const_typed_builder_derive/src/info/field.rs @@ -1,5 +1,4 @@ use crate::{ - field_kind::FieldKind, util::{inner_type, is_option}, CONST_IDENT_PREFIX, }; @@ -8,6 +7,14 @@ use quote::format_ident; /// A type alias for a collection of `FieldInfo` instances. pub type FieldCollection<'a> = Vec>; +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum FieldKind { + Optional, + Skipped, + Mandatory, + Grouped, +} + /// Represents the information about a struct field used for code generation. #[derive(Debug, PartialEq, Eq)] pub struct Field<'a> { diff --git a/const_typed_builder_derive/src/info/mod.rs b/const_typed_builder_derive/src/info/mod.rs index e57f2c0..af57a40 100644 --- a/const_typed_builder_derive/src/info/mod.rs +++ b/const_typed_builder_derive/src/info/mod.rs @@ -2,6 +2,6 @@ mod container; mod field; mod group; -pub use container::Container; -pub use field::{Field, FieldCollection}; +pub use container::{Container, SolverKind}; +pub use field::{Field, FieldCollection, FieldKind}; pub use group::{Group, GroupCollection, GroupType}; diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index 7c22132..457b2a5 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -1,8 +1,6 @@ -mod field_kind; mod generator; mod info; mod parser; -mod solver_kind; mod symbol; mod util; @@ -53,7 +51,7 @@ pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// /// An optional `TokenStream` representing the generated token stream. fn impl_my_derive(ast: &syn::DeriveInput) -> Option { - let container_info = &parser::Container::parse(ast)?; - let generator = Generator::new(container_info); + let container_info = parser::Container::new().parse(ast)?; + let generator = Generator::new(&container_info); Some(generator.generate()) } diff --git a/const_typed_builder_derive/src/parser/container.rs b/const_typed_builder_derive/src/parser/container.rs index 3f1669a..28710ac 100644 --- a/const_typed_builder_derive/src/parser/container.rs +++ b/const_typed_builder_derive/src/parser/container.rs @@ -1,11 +1,6 @@ -use std::collections::HashMap; - -use proc_macro_error::{emit_error, emit_warning}; -use syn::Token; - -use crate::{info, solver_kind::SolverKind, symbol}; - -use super::{group::Group, Field}; +use super::{Field, Group}; +use crate::{info, symbol}; +use proc_macro_error::{emit_call_site_error, emit_error, emit_warning}; /// Represents the parser for struct generation. #[derive(Debug)] @@ -14,10 +9,13 @@ pub struct Container { /// A map of group names to their respective `GroupInfo`. groups: info::GroupCollection, /// The solver used to find all possible valid combinations for the groups - solver_kind: SolverKind, + solver_kind: info::SolverKind, } impl Container { + pub fn new() -> Self { + Self::default() + } /// Updates struct settings based on provided attributes. /// /// # Arguments @@ -27,73 +25,32 @@ impl Container { /// # Returns /// /// A `syn::Result` indicating success or failure of attribute handling. - pub fn parse(ast: &syn::DeriveInput) -> Option { - match &ast { - syn::DeriveInput { - attrs, - vis, - ident, - generics, - data: - syn::Data::Struct(syn::DataStruct { - fields: syn::Fields::Named(fields), - .. - }), - } => { - let mut result = Container { - assume_mandatory: false, - groups: HashMap::new(), - solver_kind: SolverKind::BruteForce, - }; + pub fn parse(mut self, ast: &syn::DeriveInput) -> Option { + let syn::DeriveInput { + attrs, + vis, + ident, + generics, + data, + } = ast; - attrs.iter().for_each(|attr| result.handle_attribute(attr)); + attrs.iter().for_each(|attr| self.handle_attribute(attr)); - let fields = fields - .named - .iter() - .enumerate() - .map(|(index, field)| { - assert!(field.ident.is_some()); - Field::parse( - field - .ident - .as_ref() - .expect("FieldsNamed should have a named field"), - field, - &mut result, - index, - ) - }) - .collect::>(); + let fields = self.handle_data(data)?; - Some(info::Container::new( - vis, - generics, - ident, - result.groups, - fields, - result.solver_kind, - )) - } - _ => { - emit_error!(ast, "Builder is only supported for named structs",); - None - } - } - } - - pub fn assume_mandatory(&self) -> bool { - self.assume_mandatory - } - - /// Get a GroupInfo by its identifier - pub fn group_by_name_mut(&mut self, group_name: &String) -> Option<&mut info::Group> { - self.groups.get_mut(group_name) + Some(info::Container::new( + vis, + generics, + ident, + self.groups, + fields, + self.solver_kind, + )) } /// 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. + /// See the specific functions [`handle_attribute_builder`] and [`handle_attribute_group`] for more information. /// /// /// # Arguments /// @@ -122,12 +79,8 @@ impl Container { ), } match (&attr_ident.to_string()).into() { - symbol::GROUP => { - if let Some(group) = Group::parse(attr) { - self.groups.insert(group.name().to_string(), group); - } - } - symbol::BUILDER => self.handle_builder_attribute(attr), + symbol::GROUP => Group::new(&mut self.groups).parse(attr), + symbol::BUILDER => self.handle_attribute_builder(attr), _ => emit_error!(&attr, "Unknown attribute"), } } @@ -138,8 +91,6 @@ impl Container { /// 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)]`), @@ -148,7 +99,7 @@ impl Container { /// # Arguments /// /// - `attr`: A reference to the `syn::Attribute` representing the builder attribute applied to the struct. - fn handle_builder_attribute(&mut self, attr: &syn::Attribute) { + fn handle_attribute_builder(&mut self, attr: &syn::Attribute) { attr.parse_nested_meta(|meta| { let path_ident = match meta.path.require_ident() { Ok(ident) => ident, @@ -167,25 +118,11 @@ impl Container { self.assume_mandatory = true; } symbol::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() { - symbol::BRUTE_FORCE => { - self.solver_kind = SolverKind::BruteForce - } - symbol::COMPILER => self.solver_kind = SolverKind::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, "Solver type needs to be specified"); + let syn::ExprPath { path, .. } = meta.value()?.parse()?; + match (&path.require_ident()?.to_string()).into() { + symbol::BRUTE_FORCE => self.solver_kind = info::SolverKind::BruteForce, + symbol::COMPILER => self.solver_kind = info::SolverKind::Compiler, + _ => emit_error!(&path, "Unknown solver type"), } } _ => { @@ -201,4 +138,57 @@ impl Container { ) }) } + + fn handle_data<'a>(&mut self, data: &'a syn::Data) -> Option> { + match data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => self.handle_fields(fields), + syn::Data::Enum(syn::DataEnum { variants, .. }) => { + let _ = variants + .iter() + .map(|variant| self.handle_fields(&variant.fields)); + emit_call_site_error!("Builder does not *yet* support enums",); + None + } + syn::Data::Union(_) => { + emit_call_site_error!("Builder does not support unions",); + None + } + } + } + + fn handle_fields<'a>(&mut self, fields: &'a syn::Fields) -> Option>> { + match fields { + syn::Fields::Named(fields) => Some(self.handle_named_fields(fields)), + syn::Fields::Unnamed(fields) => { + emit_error!(fields, "Builder does not support unnamed fields"); + None + } + syn::Fields::Unit => Some(Vec::new()), + } + } + + fn handle_named_fields<'a>(&mut self, fields: &'a syn::FieldsNamed) -> Vec> { + fields + .named + .iter() + .enumerate() + .map(|(index, field)| { + let ident = field + .ident + .as_ref() + .expect("FieldsNamed should have an ident"); + Field::new(index, self.assume_mandatory, &mut self.groups).parse(ident, field) + }) + .collect::>() + } +} + +impl Default for Container { + fn default() -> Self { + Container { + assume_mandatory: false, + groups: info::GroupCollection::new(), + solver_kind: info::SolverKind::BruteForce, + } + } } diff --git a/const_typed_builder_derive/src/parser/field.rs b/const_typed_builder_derive/src/parser/field.rs index b824244..f1da9dc 100644 --- a/const_typed_builder_derive/src/parser/field.rs +++ b/const_typed_builder_derive/src/parser/field.rs @@ -1,79 +1,55 @@ -use crate::{field_kind::FieldKind, symbol, util::is_option}; +use crate::{info, symbol, util::is_option}; use proc_macro_error::{emit_error, emit_warning}; -use std::collections::HashSet; - -use super::Container; -use crate::info; /// Represents settings for struct field generation. -#[derive(Debug, Clone)] -pub struct Field { - kind: Option, - /// Indicates if the field should propagate values. +#[derive(Debug)] +pub struct Field<'parser> { + kind: Option, propagate: bool, - /// The groups this field belongs to. - groups: HashSet, + index: usize, + assume_mandatory: bool, + group_collection: &'parser mut info::GroupCollection, } -// impl Default for Field { -// fn default() -> Field { -// Field { -// index: 0, -// ident: syn::Ident::new("", ) -// kind: None, -// propagate: false, -// groups: HashSet::new(), -// } -// } -// } - -impl Field { - /// Creates a new `FieldSettings` instance with default values. - pub fn parse<'a>( - ident: &'a syn::Ident, - field: &'a syn::Field, - container_parser: &mut Container, +impl<'parser> Field<'parser> { + pub fn new( index: usize, - ) -> info::Field<'a> { - // let mut result = Self::default(); - let syn::Field { ty, attrs, .. } = field; - - let mut result = Self { + assume_mandatory: bool, + group_collection: &'parser mut info::GroupCollection, + ) -> Self { + Self { kind: None, propagate: false, - groups: HashSet::new(), - }; + index, + assume_mandatory, + group_collection, + } + } + + pub fn parse<'ast>( + mut self, + ident: &'ast syn::Ident, + field: &'ast syn::Field, + ) -> info::Field<'ast> { + let syn::Field { ty, attrs, .. } = field; - if result.kind.is_none() && !is_option(ty) { - result.kind = Some(FieldKind::Mandatory); // If its not an option type it MUST always be mandatory + if !is_option(ty) { + self.kind = Some(info::FieldKind::Mandatory); // If its not an option type it MUST always be mandatory } attrs .iter() - .for_each(|attr: &syn::Attribute| result.handle_attribute(attr)); + .for_each(|attr: &syn::Attribute| self.handle_attribute(attr)); - match result.kind { - Some(FieldKind::Grouped) => result.groups.iter().for_each(|group_name| { - if let Some(group) = container_parser.group_by_name_mut(&group_name.to_string()) - { - group.associate(index); - } else { - emit_error!( - group_name, - "No group called {} is available", group_name; - hint = "You might want to add a #[{}(...)] attribute to the container", symbol::GROUP - ); - } - }), - Some(_) => {} - None => if container_parser.assume_mandatory() { - result.kind = Some(FieldKind::Mandatory) + if self.kind.is_none() { + self.kind = if self.assume_mandatory { + Some(info::FieldKind::Mandatory) } else { - result.kind = Some(FieldKind::Optional) - }, + Some(info::FieldKind::Optional) + } } - info::Field::new(ident, ty, index, result.kind.unwrap(), result.propagate) + info::Field::new(ident, ty, self.index, self.kind.unwrap(), self.propagate) } /// Handles the parsing and processing of a builder attribute applied to a field. @@ -82,12 +58,13 @@ impl Field { /// `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. + /// construction. /// /// - `#[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. + /// the builder construction. + /// + /// - `#[builder(skipped)]`: Marks the field as skipped, meaning it can't be set during + /// the builder construction. /// /// - `#[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, @@ -144,10 +121,10 @@ impl Field { }; match (&path_ident.to_string()).into() { - symbol::SKIP => self.handle_skip(path_ident), - symbol::MANDATORY => self.handle_mandatory(path_ident), - symbol::OPTIONAL => self.handle_optional(path_ident), - symbol::GROUP => self.handle_group(path_ident, &meta), + symbol::SKIP => self.handle_attribute_skip(path_ident), + symbol::MANDATORY => self.handle_attribute_mandatory(path_ident), + symbol::OPTIONAL => self.handle_attribute_optional(path_ident), + symbol::GROUP => self.handle_attribute_group(path_ident, &meta), symbol::PROPAGATE => self.propagate = true, _ => emit_error!(&attr.meta, "Unknown attribute"), } @@ -161,95 +138,97 @@ impl Field { }) } - fn handle_skip(&mut self, ident: &syn::Ident) { + fn handle_attribute_skip(&mut self, ident: &syn::Ident) { match self.kind { - None => self.kind = Some(FieldKind::Skipped), - Some(FieldKind::Optional) => emit_error!( + None => self.kind = Some(info::FieldKind::Skipped), + Some(info::FieldKind::Optional) => emit_error!( ident, "Can't define field as skipped as its already defined as optional"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Skipped) => { + Some(info::FieldKind::Skipped) => { emit_warning!(ident, "Defined field as skipped multiple times") } - Some(FieldKind::Mandatory) => emit_error!( + Some(info::FieldKind::Mandatory) => emit_error!( ident, "Can't define field as skipped as its already defined as mandatory"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Grouped) => emit_error!( + Some(info::FieldKind::Grouped) => emit_error!( ident, "Can't define field as skipped when its also part of a group"; hint = "Remove either types of attribute from this field" ), } } - fn handle_mandatory(&mut self, ident: &syn::Ident) { + fn handle_attribute_mandatory(&mut self, ident: &syn::Ident) { match self.kind { - None => self.kind = Some(FieldKind::Mandatory), - Some(FieldKind::Optional) => emit_error!( + None => self.kind = Some(info::FieldKind::Mandatory), + Some(info::FieldKind::Optional) => emit_error!( ident, "Can't define field as mandatory as its already defined as optional"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Skipped) => emit_error!( + Some(info::FieldKind::Skipped) => emit_error!( ident, "Can't define field as mandatory as its already defined as skipped"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Mandatory) => { + Some(info::FieldKind::Mandatory) => { emit_warning!(ident, "Defined field as mandatory multiple times") } - Some(FieldKind::Grouped) => emit_error!( + Some(info::FieldKind::Grouped) => emit_error!( ident, "Can't define field as mandatory when its also part of a group"; hint = "Remove either types of attribute from this field" ), } } - fn handle_optional(&mut self, ident: &syn::Ident) { + fn handle_attribute_optional(&mut self, ident: &syn::Ident) { match self.kind { - None => self.kind = Some(FieldKind::Optional), - Some(FieldKind::Optional) => { + None => self.kind = Some(info::FieldKind::Optional), + Some(info::FieldKind::Optional) => { emit_warning!(ident, "Defined field as optional multiple times") } - Some(FieldKind::Skipped) => emit_error!( + Some(info::FieldKind::Skipped) => emit_error!( ident, "Can't define field as optional as its already defined as skipped"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Mandatory) => emit_error!( + Some(info::FieldKind::Mandatory) => emit_error!( ident, "Can't define field as optional as its already defined as mandatory"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Grouped) => emit_error!( + Some(info::FieldKind::Grouped) => emit_error!( ident, "Can't define field as optional when its also part of a group"; hint = "Remove either types of attribute from this field" ), } } - fn handle_group(&mut self, ident: &syn::Ident, meta: &syn::meta::ParseNestedMeta) { + fn handle_attribute_group(&mut self, ident: &syn::Ident, meta: &syn::meta::ParseNestedMeta) { match self.kind { - None => self.kind = Some(FieldKind::Grouped), - Some(FieldKind::Optional) => emit_error!( + None => self.kind = Some(info::FieldKind::Grouped), + Some(info::FieldKind::Optional) => emit_error!( ident, "Can't define field as part of a group as its already defined as optional"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Skipped) => emit_error!( + Some(info::FieldKind::Skipped) => emit_error!( ident, "Can't define field as as part of a group as its already defined as skipped"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Mandatory) => emit_error!( + Some(info::FieldKind::Mandatory) => emit_error!( ident, "Can't define field as as part of a group as its already defined as mandatory"; hint = "Remove either types of attribute from this field" ), - Some(FieldKind::Grouped) => {} + Some(info::FieldKind::Grouped) => {} } match self.extract_group_name(meta) { Ok(group_name) => { - 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); + if let Some(group) = self.group_collection.get_mut(&group_name.to_string()) { + if group.indices().contains(&self.index) { + emit_warning!( + group_name.span(), "Multiple adds to the same group"; + help = "Remove this attribute" + ) + } else { + group.associate(self.index); + } } } Err(err) => { diff --git a/const_typed_builder_derive/src/parser/group.rs b/const_typed_builder_derive/src/parser/group.rs index 0e319ce..4e9ef17 100644 --- a/const_typed_builder_derive/src/parser/group.rs +++ b/const_typed_builder_derive/src/parser/group.rs @@ -1,12 +1,16 @@ +use crate::{info, symbol}; use proc_macro_error::emit_error; -use crate::{info, symbol}; +pub struct Group<'a> { + groups: &'a mut info::GroupCollection, +} -pub struct Group; +impl<'a> Group<'a> { + pub fn new(groups: &'a mut info::GroupCollection) -> Self { + Self { groups } + } -impl Group { - pub fn parse(attr: &syn::Attribute) -> Option { - let mut group = None; + pub fn parse(self, attr: &syn::Attribute) { attr.parse_nested_meta(|meta| { let group_name = match meta.path.require_ident() { Ok(ident) => ident, @@ -21,116 +25,135 @@ impl Group { }; let group_type = match meta.value()?.parse()? { - syn::Expr::Call(syn::ExprCall { func, args, .. }) => { - let group_type = match func.as_ref() { - syn::Expr::Path(syn::ExprPath { path, .. }) => match path.require_ident() { - Ok(ident) => ident, - Err(err) => { - emit_error!( - &meta.path , "Group type is not specified correctly"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST; - note = err - ); - return Ok(()); - } - }, - _ => { - emit_error!( - &attr.meta, "No group type specified"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST - ); - return Ok(()); - } - }; - - 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() { - symbol::EXACT => info::GroupType::Exact(group_args), - symbol::AT_LEAST => info::GroupType::AtLeast(group_args), - symbol::AT_MOST => info::GroupType::AtMost(group_args), - symbol::SINGLE => { - emit_error!( - args, - "`{}` doesn't take any arguments", symbol::SINGLE; - help = "`{}` is shorthand for {}(1)", symbol::SINGLE, symbol::EXACT - ); - return Ok(()); - } - _ => { - emit_error!( - group_type, "Unknown group type"; - help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST - ); - return Ok(()); - } - }, - Err(err) => { - emit_error!( - val, "Couldn't parse group argument"; - note = err - ); - return Ok(()); - } - }, - - _ => { - emit_error!(func, "Can't parse group argument"); - return Ok(()); - } - } - } - syn::Expr::Path(syn::ExprPath { path, .. }) => { - let group_type = match path.require_ident() { - Ok(ident) => ident, - Err(err) => { - emit_error!( - &meta.path , "Group type is not specified correctly"; - help = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST; - note = err - ); - return Ok(()); - } - }; - match (&group_type.to_string()).into() { - symbol::EXACT | symbol::AT_LEAST | symbol::AT_MOST => { - emit_error!( - &attr.meta, - "Missing arguments for group type"; - help = "Try `{}(1)`, or any other usize", &group_type - ); - return Ok(()); - } - symbol::SINGLE => info::GroupType::Exact(1), - _ => { - emit_error!( - group_type, - "Unknown group type"; - help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST - ); - return Ok(()); - } - } - } + syn::Expr::Call(expr) => self.handle_group_call(&expr), + syn::Expr::Path(expr) => self.handle_group_path(&expr), _ => { emit_error!( - &attr.meta, "No group type specified"; + &attr.meta, "Can't parse group type"; hint = "Try to define it like `#[group({} = {}(1))]`", group_name, symbol::AT_LEAST ); return Ok(()); } }; - group = Some(info::Group::new(group_name.clone(), group_type)); + + if let Some(group_type) = group_type { + if let Some(earlier_definition) = self.groups.insert(group_name.to_string(), info::Group::new(group_name.clone(), group_type)) { + let earlier_span = earlier_definition.name().span(); + emit_error!( + &group_name, "Group defined multiple times"; + help = earlier_span => "Also defined here" + ); + } + } Ok(()) }) .unwrap_or_else(|err| emit_error!( - &attr.meta, "Unknown error"; + &attr.meta, "Error occured while parsing group"; note = err )); - group + } + + fn handle_group_call(&self, expr: &syn::ExprCall) -> Option { + let syn::ExprCall { func, args, .. } = expr; + + let type_ident = match func.as_ref() { + syn::Expr::Path(syn::ExprPath { path, .. }) => match path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!( + &expr , "Group type is not specified correctly"; + help = "Try to define it like `#[group(foo = {}(1))]`", symbol::AT_LEAST; + note = err + ); + return None; + } + }, + _ => { + emit_error!( + &expr, "No group type specified"; + help = "Try to define it like `#[group(foo = {}(1))]`", symbol::AT_LEAST + ); + return None; + } + }; + + if args.len() != 1 { + emit_error!(func, "Group needs exactly one integer literal as argument"); + return None; + } + + let group_argument = match args.first().unwrap() { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(val), + .. + }) => val.base10_parse::().ok(), + _ => None, + }; + + let group_argument = match group_argument { + Some(lit) => lit, + None => { + emit_error!(args, "Can't parse argument"); + return None; + } + }; + + let group_type = match (&type_ident.to_string()).into() { + symbol::EXACT => info::GroupType::Exact(group_argument), + symbol::AT_LEAST => info::GroupType::AtLeast(group_argument), + symbol::AT_MOST => info::GroupType::AtMost(group_argument), + symbol::SINGLE => { + emit_error!( + args, + "`{}` is the only group type that doesn't take any arguments", symbol::SINGLE; + help = "`{}` is shorthand for {}(1)", symbol::SINGLE, symbol::EXACT + ); + return None; + } + _ => { + emit_error!( + type_ident, "Unknown group type"; + help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST + ); + return None; + } + }; + Some(group_type) + } + + fn handle_group_path(&self, expr: &syn::ExprPath) -> Option { + let syn::ExprPath { path, .. } = expr; + let type_ident = match path.require_ident() { + Ok(ident) => ident, + Err(err) => { + emit_error!( + &expr , "Group type is not specified correctly"; + help = "Try to define it like `#[group(foo = {}(1))]`", symbol::AT_LEAST; + note = err + ); + return None; + } + }; + + match (&type_ident.to_string()).into() { + symbol::SINGLE => Some(info::GroupType::Exact(1)), + symbol::EXACT | symbol::AT_LEAST | symbol::AT_MOST => { + emit_error!( + &expr, + "Missing arguments for group type"; + help = "Try `{}(1)`, or any other usize", &type_ident + ); + None + } + _ => { + emit_error!( + type_ident, + "Unknown group type"; + help = "Known group types are {}, {} and {}", symbol::EXACT, symbol::AT_LEAST, symbol::AT_MOST + ); + None + } + } } } diff --git a/const_typed_builder_derive/src/parser/mod.rs b/const_typed_builder_derive/src/parser/mod.rs index bc3a865..f38baa0 100644 --- a/const_typed_builder_derive/src/parser/mod.rs +++ b/const_typed_builder_derive/src/parser/mod.rs @@ -3,4 +3,5 @@ mod field; mod group; pub use container::Container; -pub use field::Field; +use field::Field; +use group::Group; diff --git a/const_typed_builder_derive/src/solver_kind.rs b/const_typed_builder_derive/src/solver_kind.rs deleted file mode 100644 index d2b62a9..0000000 --- a/const_typed_builder_derive/src/solver_kind.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[derive(Debug, Clone, Copy)] -pub enum SolverKind { - BruteForce, - Compiler, -} From cb4e9f54e031b2587c7cc17b7122f4b185cbcff7 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:45:34 +0200 Subject: [PATCH 04/15] Remove field group and generics generator. Also add generate mod (now always false) --- .../src/generator/builder_generator.rs | 550 ++++++++++++------ .../src/generator/data_generator.rs | 138 +++-- .../src/generator/field_generator.rs | 170 ------ .../src/generator/generics_generator.rs | 211 ------- .../src/generator/group_generator.rs | 173 ------ .../src/generator/mod.rs | 127 ++-- .../src/generator/target_generator.rs | 50 +- .../src/info/container.rs | 30 +- const_typed_builder_derive/src/info/group.rs | 35 +- .../src/parser/group.rs | 2 +- const_typed_builder_test/Cargo.toml | 2 +- 11 files changed, 635 insertions(+), 853 deletions(-) delete mode 100644 const_typed_builder_derive/src/generator/field_generator.rs delete mode 100644 const_typed_builder_derive/src/generator/generics_generator.rs delete mode 100644 const_typed_builder_derive/src/generator/group_generator.rs diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index fdaf037..7001a7d 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -1,67 +1,19 @@ -use super::{ - field_generator::FieldGenerator, generics_generator::GenericsGenerator, - group_generator::GroupGenerator, -}; -use crate::{info::FieldKind, info::SolverKind}; -use convert_case::{Case, Casing}; +use super::util; +use crate::info; +use either::Either; +use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; -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. -pub(super) struct BuilderGenerator<'a> { - group_gen: GroupGenerator<'a>, - field_gen: FieldGenerator<'a>, - generics_gen: GenericsGenerator<'a>, - target_name: &'a syn::Ident, - target_vis: &'a syn::Visibility, - builder_name: &'a syn::Ident, - data_name: &'a syn::Ident, - solve_type: SolverKind, -} +use quote::quote; +use std::collections::BTreeSet; +use syn::{parse_quote, GenericParam}; -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. - /// - `solve_type`: The type of solver employed for validating the grouped fields - /// - /// # 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>, - generics_gen: GenericsGenerator<'a>, - target_name: &'a syn::Ident, - target_vis: &'a syn::Visibility, - builder_name: &'a syn::Ident, - data_name: &'a syn::Ident, - solve_type: SolverKind, - ) -> Self { - Self { - group_gen, - field_gen, - generics_gen, - target_name, - target_vis, - builder_name, - data_name, - solve_type, - } - } +pub struct BuilderGenerator<'info> { + info: &'info info::Container<'info>, +} - fn data_field_ident(&self) -> syn::Ident { - format_ident!("__{}", self.data_name.to_string().to_case(Case::Snake)) +impl<'info> BuilderGenerator<'info> { + pub fn new(info: &'info info::Container) -> Self { + Self { info } } // Generates the code for the builder struct and its methods and returns a token stream. @@ -72,35 +24,34 @@ impl<'a> BuilderGenerator<'a> { pub fn generate(&self) -> TokenStream { let builder_struct = self.generate_struct(); let builder_impl = self.generate_impl(); - let tokens = quote!( + quote!( #builder_struct #builder_impl - ); - tokens + ) } /// 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 data_ident = self.info.data_ident(); + let data_field = self.info.data_field_ident(); + let builder_ident = self.info.builder_ident(); - let generics = self.generics_gen.builder_struct_generics(); + let generics = self.struct_generics(); let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, type_generics, _) = self.generics_gen.target_generics().split_for_impl(); + let (_, type_generics, _) = self.info.generics().split_for_impl(); - let vis = self.target_vis; + let vis = self.info.vis(); let documentation = format!( "Builder for [`{}`] derived using the `const_typed_builder` crate", - self.target_name + self.info.ident() ); quote!( #[doc = #documentation] - #vis struct #builder_name #impl_generics #where_clause { - #data_field: #data_name #type_generics + #vis struct #builder_ident #impl_generics #where_clause { + #data_field: #data_ident #type_generics } ) } @@ -111,40 +62,37 @@ impl<'a> BuilderGenerator<'a> { let builder_new = self.generate_new_impl(); let builder_build = self.generate_build_impl(); - let tokens = quote!( + quote!( #builder_new #builder_setters #builder_build - ); - 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; - let data_field = self.data_field_ident(); + let builder_ident = self.info.builder_ident(); + let data_ident = self.info.data_ident(); + let data_field = self.info.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(); - let documentation = format!( - "Creates a new [`{}`] without any fields initialized", - self.builder_name - ); + let type_generics = util::const_generics_all_valued(false, self.info.field_collection(), self.info.generics()); + let (impl_generics, _, where_clause) = self.info.generics().split_for_impl(); + let documentation = + format!("Creates a new [`{builder_ident}`] without any fields initialized"); quote!( - impl #impl_generics #builder_name #type_generics #where_clause{ + impl #impl_generics #builder_ident #type_generics #where_clause{ #[doc = #documentation] - pub fn new() -> #builder_name #type_generics { + pub fn new() -> #builder_ident #type_generics { Self::default() } } - impl #impl_generics Default for #builder_name #type_generics #where_clause { + impl #impl_generics Default for #builder_ident #type_generics #where_clause { + #[doc = #documentation] fn default() -> Self { - #[doc = #documentation] - #builder_name { - #data_field: #data_name::default(), + #builder_ident { + #data_field: #data_ident::default(), } } } @@ -153,65 +101,53 @@ 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 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 builder_ident = self.info.builder_ident(); + let target_ident = self.info.ident(); + let data_field = self.info.data_field_ident(); + let documentation = + format!("Build an instance of [`{target_ident}`], consuming the [`{builder_ident}`]"); + let (impl_generics, target_type_generics, where_clause) = - self.generics_gen.target_generics().split_for_impl(); - - match self.solve_type { - SolverKind::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{ - #[doc = #documentation] - pub fn build(self) -> #target_name #target_type_generics { - self.#data_field.into() - } - } - ) - }); + self.info.generics().split_for_impl(); + + match self.info.solve_type() { + info::SolverKind::BruteForce => { + let build_impls = self.valid_groupident_combinations().map(|group_indices| { + let type_generics = self.const_generic_idents_build(&group_indices); + + quote!( + impl #impl_generics #builder_ident #type_generics #where_clause{ + #[doc = #documentation] + pub fn build(self) -> #target_ident #target_type_generics { + self.#data_field.into() + } + } + ) + }); quote!( #(#build_impls)* ) } - SolverKind::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(); + info::SolverKind::Compiler => { + let builder_ident = self.info.builder_ident(); + let impl_generics = self.const_generic_group_partial_idents(); + let type_generics = self.const_generic_idents_build_unset_group(); + + let correctness_verifier = self.impl_correctness_verifier(); + let correctness_check = self.impl_correctness_check(); + let correctness_helper_fns = self.impl_correctness_helper_fns(); + + let target_ident = self.info.ident(); + let (_, target_type_generics, where_clause) = self.info.generics().split_for_impl(); quote!( - impl #impl_generics #builder_name #type_generics #where_clause{ + impl #impl_generics #builder_ident #type_generics #where_clause{ #correctness_verifier #correctness_helper_fns #[doc = #documentation] - pub fn build(self) -> #target_name #target_type_generics { + pub fn build(self) -> #target_ident #target_type_generics { #correctness_check self.#data_field.into() } @@ -223,52 +159,340 @@ impl<'a> BuilderGenerator<'a> { /// Generates the code for the setter methods of the builder. fn generate_setters_impl(&self) -> TokenStream { - let builder_name = self.builder_name; - let data_field = self.data_field_ident(); + let builder_ident = self.info.builder_ident(); + let data_field = self.info.data_field_ident(); let setters = self - .field_gen - .fields() + .info + .field_collection() .iter() - .filter(|field| field.kind() != &FieldKind::Skipped) + .filter(|field| field.kind() != &info::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); - let where_clause = &self.generics_gen.target_generics().where_clause; + let const_idents_impl = self.const_generic_idents_set_impl(field); + let const_idents_type_input = self.const_generic_idents_set_type(field, false); + let const_idents_type_output = self.const_generic_idents_set_type(field, true); + let where_clause = &self.info.generics().where_clause; - let field_name = field.ident(); - 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 field_ident = field.ident(); + let input_type = self.set_impl_input_type(field); + let input_value = self.set_impl_input_value(field); let documentation = format!(r#" -Setter for the [`{}::{field_name}`] field. +Setter for the [`{}::{field_ident}`] field. # Arguments -- `{field_name}`: field to be set +- `{field_ident}`: field to be set # Returns -`Self` with `{field_name}` initialized"#, self.target_name); +`{builder_ident}` with `{field_ident}` initialized"#, self.info.ident()); - let tokens = quote!( - impl #const_idents_impl #builder_name #const_idents_type_input #where_clause { + quote!( + impl #const_idents_impl #builder_ident #const_idents_type_input #where_clause { #[doc = #documentation] - pub fn #field_name (self, #input_type) -> #builder_name #const_idents_type_output { + pub fn #field_ident (self, #input_type) -> #builder_ident #const_idents_type_output { let mut #data_field = self.#data_field; - #data_field.#field_name = #input_value; - #builder_name { + #data_field.#field_ident = #input_value; + #builder_ident { #data_field, } } } - ); - tokens + ) }); - let tokens = quote!( + quote!( #(#setters)* - ); - tokens + ) + } + + fn struct_generics(&self) -> syn::Generics { + let mut all = self + .info + .field_collection() + .iter() + .filter_map(|field| match field.kind() { + info::FieldKind::Skipped | info::FieldKind::Optional => None, + info::FieldKind::Mandatory | info::FieldKind::Grouped => Some(field.const_ident()), + }); + self.add_const_generics_for_impl(&mut all) + } + + fn const_generic_idents_build(&self, true_indices: &[usize]) -> TokenStream { + let mut all = self + .info + .field_collection() + .iter() + .filter_map(|field| match field.kind() { + info::FieldKind::Skipped | info::FieldKind::Optional => None, + info::FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( + true, + proc_macro2::Span::call_site(), + ))), + info::FieldKind::Grouped if true_indices.contains(&field.index()) => Some( + Either::Right(syn::LitBool::new(true, proc_macro2::Span::call_site())), + ), + info::FieldKind::Grouped => Some(Either::Right(syn::LitBool::new( + false, + proc_macro2::Span::call_site(), + ))), + }); + util::add_const_valued_generics_for_type(&mut all, self.info.generics()) + } + + fn const_generic_idents_set_impl(&self, field_info: &info::Field) -> syn::Generics { + let mut all = self + .info + .field_collection() + .iter() + .filter_map(|field| match field.kind() { + info::FieldKind::Skipped | info::FieldKind::Optional => None, + _ if field == field_info => None, + info::FieldKind::Mandatory | info::FieldKind::Grouped => Some(field.const_ident()), + }); + self.add_const_generics_for_impl(&mut all) + } + + fn const_generic_idents_set_type(&self, field_info: &info::Field, value: bool) -> TokenStream { + let mut all = self + .info + .field_collection() + .iter() + .filter_map(|field| match field.kind() { + info::FieldKind::Skipped | info::FieldKind::Optional => None, + _ if field == field_info => Some(Either::Right(syn::LitBool::new( + value, + field_info.ident().span(), + ))), + info::FieldKind::Mandatory | info::FieldKind::Grouped => { + Some(Either::Left(field.const_ident())) + } + }); + util::add_const_valued_generics_for_type(&mut all, self.info.generics()) + } + + fn const_generic_group_partial_idents(&self) -> syn::Generics { + let mut all = self + .info + .field_collection() + .iter() + .filter_map(|field| match field.kind() { + info::FieldKind::Skipped + | info::FieldKind::Optional + | info::FieldKind::Mandatory => None, + info::FieldKind::Grouped => Some(field.const_ident()), + }); + self.add_const_generics_for_impl(&mut all) + } + + fn const_generic_idents_build_unset_group(&self) -> TokenStream { + let mut all = self + .info + .field_collection() + .iter() + .filter_map(|field| match field.kind() { + info::FieldKind::Skipped | info::FieldKind::Optional => None, + info::FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( + true, + proc_macro2::Span::call_site(), + ))), + info::FieldKind::Grouped => Some(Either::Left(field.const_ident())), + }); + util::add_const_valued_generics_for_type(&mut all, self.info.generics()) + } + + fn impl_correctness_verifier(&self) -> TokenStream { + if self.info.groups().is_empty() { + return TokenStream::new(); + } + + let all = self.info.groups().values().map(|group| { + let partials = group.indices().iter().map(|index| self.info.field_collection().get(*index).expect("Could not find field associated to group").const_ident()); + let function_call: syn::Ident = group.function_symbol().into(); + let count = group.expected_count(); + let ident = group.ident(); + let function_ident = group.function_symbol().to_string(); + let err_text = format!("`.build()` failed because the bounds of group `{ident}` where not met ({function_ident} {count})"); + + quote!( + if !Self::#function_call(&[#(#partials),*], #count) { + panic!(#err_text); + } + ) + }); + quote!( + const GROUP_VERIFIER: () = { + #(#all)* + () + }; + ) + } + + fn impl_correctness_check(&self) -> TokenStream { + if self.info.groups().is_empty() { + TokenStream::new() + } else { + quote!(let _ = Self::GROUP_VERIFIER;) + } + } + + fn impl_correctness_helper_fns(&self) -> TokenStream { + if self.info.groups().is_empty() { + return TokenStream::new(); + } + + let mut exact = false; + let mut at_least = false; + let mut at_most = false; + + for group in self.info.groups().values() { + match group.group_type() { + info::GroupType::Exact(_) => exact = true, + info::GroupType::AtLeast(_) => at_least = true, + info::GroupType::AtMost(_) => at_most = true, + } + + if exact && at_least && at_most { + break; + } + } + + let exact = exact.then(|| { + quote!( + const fn exact(input: &[bool], count: usize) -> bool { + let mut this_count = 0; + let mut i = 0; + while i < input.len() { + if input[i] { + this_count += 1 + } + i += 1; + } + this_count == count + } + ) + }); + + let at_least = at_least.then(|| { + quote!( + const fn at_least(input: &[bool], count: usize) -> bool { + let mut this_count = 0; + let mut i = 0; + while i < input.len() { + if input[i] { + this_count += 1 + } + i += 1; + } + this_count >= count + } + ) + }); + + let at_most = at_most.then(|| { + quote!( + const fn at_most(input: &[bool], count: usize) -> bool { + let mut this_count = 0; + let mut i = 0; + while i < input.len() { + if input[i] { + this_count += 1 + } + i += 1; + } + this_count <= count + } + ) + }); + quote!( + #exact + #at_least + #at_most + ) + } + + fn set_impl_input_type(&self, field: &'info info::Field) -> Option { + if field.kind() == &info::FieldKind::Skipped { + return None; + } + let field_ident = field.ident(); + let field_ty = field.setter_input_type(); + let bottom_ty = if field.is_option_type() { + field.inner_type().unwrap() + } else { + field_ty.unwrap() + }; + + let field_ty = if field.propagate() { + quote!(fn(<#bottom_ty as Builder>:: BuilderImpl) -> #field_ty) + } else { + quote!(#field_ty) + }; + + Some(quote!(#field_ident: #field_ty)) + } + + fn set_impl_input_value(&self, field: &'info info::Field) -> Option { + if field.kind() == &info::FieldKind::Skipped { + return None; + } + + let field_ident = field.ident(); + let field_ty = field.setter_input_type(); + let bottom_ty = if field.is_option_type() { + field.inner_type().unwrap() + } else { + field_ty.unwrap() + }; + + let field_value = if field.propagate() { + quote!(#field_ident(<#bottom_ty as Builder>::builder())) + } else { + quote!(#field_ident) + }; + + if field.kind() == &info::FieldKind::Optional { + Some(quote!(#field_value)) + } else { + Some(quote!(Some(#field_value))) + } + } + + fn valid_groupident_combinations(&self) -> impl Iterator> + '_ { + let group_indices: BTreeSet = self + .info + .groups() + .values() + .flat_map(|group| group.indices().clone()) + .collect(); + let powerset: Powerset> = + group_indices.into_iter().powerset(); + powerset.filter_map(|set| { + if self + .info + .groups() + .values() + .all(|group| group.is_valid_with(&set)) + { + Some(set) + } else { + None + } + }) + } + + /// 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, + ) -> syn::Generics { + let mut res = self.info.generics().clone(); + res.params + .extend(tokens.map::(|token| parse_quote!(const #token: bool))); + res } } diff --git a/const_typed_builder_derive/src/generator/data_generator.rs b/const_typed_builder_derive/src/generator/data_generator.rs index 8127f1c..f5aa137 100644 --- a/const_typed_builder_derive/src/generator/data_generator.rs +++ b/const_typed_builder_derive/src/generator/data_generator.rs @@ -1,14 +1,11 @@ -use super::{field_generator::FieldGenerator, generics_generator::GenericsGenerator}; +use crate::info; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; /// 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>, - target_name: &'a syn::Ident, - data_name: &'a syn::Ident, + info: &'a info::Container<'a>, } impl<'a> DataGenerator<'a> { @@ -18,24 +15,14 @@ impl<'a> DataGenerator<'a> { /// /// - `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. + /// - `target_ident`: A reference to the identifier representing the target struct's ident. + /// - `data_ident`: A reference to the identifier representing the data struct's ident. /// /// # Returns /// /// A `DataGenerator` instance initialized with the provided information. - pub(super) fn new( - field_gen: FieldGenerator<'a>, - generics_gen: GenericsGenerator<'a>, - target_name: &'a syn::Ident, - data_name: &'a syn::Ident, - ) -> Self { - Self { - field_gen, - generics_gen, - target_name, - data_name, - } + pub(super) fn new(info: &'a info::Container<'a>) -> Self { + Self { info } } /// Generates the code for the data struct and the conversion implementations and returns a token stream. @@ -57,28 +44,27 @@ impl<'a> DataGenerator<'a> { /// Generates the implementation code for conversions between the data struct and the target struct. 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 def_fields = self.field_gen.data_impl_default_fields(); + let data_ident = self.info.data_ident(); + let struct_ident = self.info.ident(); + let from_fields = self.impl_from_fields(); + let def_fields = self.impl_default_fields(); - let (impl_generics, type_generics, where_clause) = - self.generics_gen.target_generics().split_for_impl(); + let (impl_generics, type_generics, where_clause) = self.info.generics().split_for_impl(); let tokens = quote!( - impl #impl_generics From<#data_name #type_generics> for #struct_name #type_generics #where_clause { + impl #impl_generics From<#data_ident #type_generics> for #struct_ident #type_generics #where_clause { #[doc(hidden)] - fn from(data: #data_name #type_generics) -> #struct_name #type_generics { - #struct_name { + fn from(data: #data_ident #type_generics) -> #struct_ident #type_generics { + #struct_ident { #(#from_fields),* } } } - impl #impl_generics Default for #data_name #type_generics #where_clause { + impl #impl_generics Default for #data_ident #type_generics #where_clause { #[doc(hidden)] fn default() -> Self { - #data_name { + #data_ident { #def_fields } } @@ -89,18 +75,98 @@ impl<'a> DataGenerator<'a> { /// Generates the code for the data struct itself. fn generate_struct(&self) -> TokenStream { - let data_name = self.data_name; + let data_ident = self.info.data_ident(); - let fields = self.field_gen.data_struct_fields(); - let (impl_generics, _type_generics, where_clause) = - self.generics_gen.target_generics().split_for_impl(); + let fields = self.struct_fields(); + let (impl_generics, _type_generics, where_clause) = self.info.generics().split_for_impl(); let tokens = quote!( #[doc(hidden)] - pub struct #data_name #impl_generics #where_clause{ + pub struct #data_ident #impl_generics #where_clause{ #(#fields),* } ); tokens } + + /// Generates code for the fields of the data struct and returns a token stream. + /// + /// # Returns + /// + /// A `Vec` representing the data struct fields: `pub field_ident: field_type`. + fn struct_fields(&self) -> Vec { + self.info + .field_collection() + .iter() + .filter_map(|field| { + let field_ident = field.ident(); + + let data_field_type = match field.kind() { + info::FieldKind::Skipped => return None, + info::FieldKind::Optional => field.ty().to_token_stream(), + info::FieldKind::Mandatory if field.is_option_type() => { + field.ty().to_token_stream() + } + info::FieldKind::Mandatory => { + let ty = field.ty(); + quote!(Option<#ty>) + } + info::FieldKind::Grouped => field.ty().to_token_stream(), + }; + + let tokens = quote!( + pub #field_ident: #data_field_type + ); + Some(tokens) + }) + .collect() + } + + // Generates code for the `From` trait implementation for converting data struct fields to target struct fields and returns a token stream. + /// + /// # Returns + /// + /// A `Vec` representing the fields for the `From` trait implementation. Either containing `unwrap`, `None` or just the type. + fn impl_from_fields(&self) -> Vec { + self.info + .field_collection() + .iter() + .map(|field| { + let field_ident = field.ident(); + let tokens = match field.kind() { + info::FieldKind::Skipped => quote!(#field_ident: None), + info::FieldKind::Mandatory if field.is_option_type() => { + quote!(#field_ident: data.#field_ident) + } + info::FieldKind::Optional | info::FieldKind::Grouped => { + quote!(#field_ident: data.#field_ident) + } + info::FieldKind::Mandatory => { + quote!(#field_ident: data.#field_ident.unwrap()) + } + }; + tokens + }) + .collect() + } + + /// Generates default field values for the data struct and returns a token stream. + /// + /// # Returns + /// + /// A `TokenStream` representing the generated default field values. + fn impl_default_fields(&self) -> TokenStream { + let fields_none = self + .info + .field_collection() + .iter() + .filter(|field| field.kind() != &info::FieldKind::Skipped) + .map(|field| { + let field_ident = field.ident(); + quote!(#field_ident: None) + }); + quote!( + #(#fields_none),* + ) + } } diff --git a/const_typed_builder_derive/src/generator/field_generator.rs b/const_typed_builder_derive/src/generator/field_generator.rs deleted file mode 100644 index b86710b..0000000 --- a/const_typed_builder_derive/src/generator/field_generator.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::info::{Field, FieldCollection, FieldKind}; -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 FieldCollection<'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 FieldCollection<'a>) -> Self { - Self { fields } - } - - /// Returns a reference to the fields of the struct. - pub fn fields(&self) -> &[Field] { - self.fields - } - - /// Generates code for the fields of the data struct and returns a token stream. - /// - /// # Returns - /// - /// A `Vec` representing the data struct fields: `pub field_name: field_type`. - pub fn data_struct_fields(&self) -> Vec { - self.fields - .iter() - .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 => { - let ty = field.ty(); - quote!(Option<#ty>) - } - FieldKind::Grouped => field.ty().to_token_stream(), - }; - - let tokens = quote!( - pub #field_name: #data_field_type - ); - Some(tokens) - }) - .collect() - } - - // Generates code for the `From` trait implementation for converting data struct fields to target struct fields and returns a token stream. - /// - /// # Returns - /// - /// 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() - .map(|field| { - let field_name = field.ident(); - let tokens = match field.kind() { - FieldKind::Skipped => quote!(#field_name: None), - FieldKind::Mandatory if field.is_option_type() => { - quote!(#field_name: data.#field_name) - } - FieldKind::Optional | FieldKind::Grouped => { - quote!(#field_name: data.#field_name) - } - FieldKind::Mandatory => { - quote!(#field_name: data.#field_name.unwrap()) - } - }; - tokens - }) - .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() - .filter(|field| field.kind() != &FieldKind::Skipped) - .map(|field| { - let field_name = field.ident(); - quote!(#field_name: None) - }); - quote!( - #(#fields_none),* - ) - } - - /// 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 `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 Field) -> 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.unwrap() - }; - - let field_ty = if field.propagate() { - quote!(fn(<#bottom_ty as Builder>:: BuilderImpl) -> #field_ty) - } else { - quote!(#field_ty) - }; - - Some(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 `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 Field) -> 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.unwrap() - }; - - let field_value = if field.propagate() { - quote!(#field_name(<#bottom_ty as Builder>::builder())) - } else { - quote!(#field_name) - }; - - if field.kind() == &FieldKind::Optional { - Some(quote!(#field_value)) - } else { - 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 deleted file mode 100644 index 3ddb667..0000000 --- a/const_typed_builder_derive/src/generator/generics_generator.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::info::{Field, FieldCollection, FieldKind}; -use either::Either; -use proc_macro2::TokenStream; -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. -#[derive(Debug, Clone)] -pub(super) struct GenericsGenerator<'a> { - pub fields: &'a FieldCollection<'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 FieldCollection<'a>, target_generics: &'a syn::Generics) -> Self { - Self { - fields, - target_generics, - } - } - - /// 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().filter_map(|field| match field.kind() { - FieldKind::Skipped | 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) - } - - /// 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: &Field, - value: bool, - ) -> TokenStream { - let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Skipped | 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) - } - - /// 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: &Field) -> syn::Generics { - let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Skipped | FieldKind::Optional => None, - _ if field == field_info => None, - FieldKind::Mandatory | FieldKind::Grouped => Some(field.const_ident()), - }); - 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::Skipped | 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_unset_group(&self) -> TokenStream { - let mut all = self.fields.iter().filter_map(|field| match field.kind() { - FieldKind::Skipped | FieldKind::Optional => None, - FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( - true, - proc_macro2::Span::call_site(), - ))), - FieldKind::Grouped => Some(Either::Left(field.const_ident())), - }); - 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().filter_map(|field| match field.kind() { - FieldKind::Skipped | FieldKind::Optional | FieldKind::Mandatory => None, - FieldKind::Grouped => Some(field.const_ident()), - }); - 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().filter_map(|field| match field.kind() { - FieldKind::Skipped | FieldKind::Optional => None, - FieldKind::Mandatory | FieldKind::Grouped => Some(field.const_ident()), - }); - 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, - ) -> syn::Generics { - let mut res = self.target_generics.clone(); - - tokens.for_each(|token| { - res.params.push(parse_quote!(const #token: bool)); - }); - 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>, - ) -> TokenStream { - let params = &self.target_generics.params; - let tokens: Vec = constants - .map(|constant| { - constant - .map_either(|iden| iden.to_token_stream(), |lit| lit.to_token_stream()) - .into_inner() - }) - .collect(); - if params.is_empty() { - quote!(<#(#tokens),*>) - } else { - let type_generics = params.iter().map(|param| match param { - syn::GenericParam::Lifetime(lt) => <.lifetime.ident, - syn::GenericParam::Type(ty) => &ty.ident, - syn::GenericParam::Const(cnst) => &cnst.ident, - }); - quote!(< #(#type_generics),*, #(#tokens),* >) - } - } -} diff --git a/const_typed_builder_derive/src/generator/group_generator.rs b/const_typed_builder_derive/src/generator/group_generator.rs deleted file mode 100644 index 6d37b5e..0000000 --- a/const_typed_builder_derive/src/generator/group_generator.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::{ - info::{Group, GroupType}, - CONST_IDENT_PREFIX, -}; -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)] -pub(super) struct GroupGenerator<'a> { - groups: Vec<&'a Group>, -} - -impl<'a> GroupGenerator<'a> { - /// Creates a new `GroupGenerator` instance, first checking if the groups can be valid. - /// - /// # 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 Group>) -> Self { - groups.iter().for_each(|group| group.check()); - 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 - .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 - /// - /// 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(); - } - - let mut exact = false; - let mut at_least = false; - let mut at_most = false; - - for group in &self.groups { - match group.group_type() { - GroupType::Exact(_) => exact = true, - GroupType::AtLeast(_) => at_least = true, - GroupType::AtMost(_) => at_most = true, - } - - if exact && at_least && at_most { - break; - } - } - - let exact = exact.then(|| { - quote!( - const fn exact(input: &[bool], count: usize) -> bool { - let mut this_count = 0; - let mut i = 0; - while i < input.len() { - if input[i] { - this_count += 1 - } - i += 1; - } - this_count == count - } - ) - }); - - let at_least = at_least.then(|| { - quote!( - const fn at_least(input: &[bool], count: usize) -> bool { - let mut this_count = 0; - let mut i = 0; - while i < input.len() { - if input[i] { - this_count += 1 - } - i += 1; - } - this_count >= count - } - ) - }); - - let at_most = at_most.then(|| { - quote!( - const fn at_most(input: &[bool], count: usize) -> bool { - let mut this_count = 0; - let mut i = 0; - while i < input.len() { - if input[i] { - this_count += 1 - } - i += 1; - } - this_count <= count - } - ) - }); - quote!( - #exact - #at_least - #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; - } - - 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(); - let function_name = group.function_symbol().to_string(); - let err_text = format!("`.build()` failed because the bounds of group `{name}` where not met ({function_name} {count})"); - - quote!( - if !Self::#function_call(&[#(#partials),*], #count) { - panic!(#err_text); - } - ) - }); - Some(quote!( - const GROUP_VERIFIER: () = { - #(#all)* - () - }; - )) - } -} diff --git a/const_typed_builder_derive/src/generator/mod.rs b/const_typed_builder_derive/src/generator/mod.rs index 2d5690a..ca9392c 100644 --- a/const_typed_builder_derive/src/generator/mod.rs +++ b/const_typed_builder_derive/src/generator/mod.rs @@ -1,27 +1,24 @@ mod builder_generator; mod data_generator; -mod field_generator; -mod generics_generator; -mod group_generator; mod target_generator; use self::{ builder_generator::BuilderGenerator, data_generator::DataGenerator, - field_generator::FieldGenerator, generics_generator::GenericsGenerator, - group_generator::GroupGenerator, target_generator::TargetGenerator, + target_generator::TargetGenerator, }; -use crate::info::Container; +use crate::info::{Container, self}; use proc_macro2::TokenStream; 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>, - builder_gen: BuilderGenerator<'a>, +pub struct Generator<'info> { + info: &'info info::Container<'info>, + data_gen: DataGenerator<'info>, + target_gen: TargetGenerator<'info>, + builder_gen: BuilderGenerator<'info>, } -impl<'a> Generator<'a> { +impl<'info> Generator<'info> { /// Creates a new `Generator` instance for code generation. /// /// # Arguments @@ -31,32 +28,14 @@ impl<'a> Generator<'a> { /// # Returns /// /// A `Generator` instance initialized with the provided `StructInfo`. - pub fn new(info: &'a Container<'a>) -> Self { - let generics_gen = GenericsGenerator::new(info.field_collection(), info.generics()); - let field_gen = FieldGenerator::new(info.field_collection()); - let group_gen = GroupGenerator::new(info.groups().values().collect()); + pub fn new(info: &'info Container<'info>) -> Self { + info.groups().values().for_each(|group| group.check()); + Generator { - data_gen: DataGenerator::new( - field_gen.clone(), - generics_gen.clone(), - info.name(), - info.data_name(), - ), - target_gen: TargetGenerator::new( - generics_gen.clone(), - info.name(), - info.builder_name(), - ), - builder_gen: BuilderGenerator::new( - group_gen, - field_gen, - generics_gen, - info.name(), - info.vis(), - info.builder_name(), - info.data_name(), - info.solve_type(), - ), + info, + data_gen: DataGenerator::new(info), + target_gen: TargetGenerator::new(info), + builder_gen: BuilderGenerator::new(info), } } @@ -69,11 +48,75 @@ impl<'a> Generator<'a> { let target = self.target_gen.generate(); let data = self.data_gen.generate(); let builder = self.builder_gen.generate(); - let tokens = quote!( - #target - #builder - #data - ); - tokens + + if self.info.generate_module() { + let mod_ident = self.info.mod_ident(); + let target_ident = self.info.ident(); + quote!( + #target + mod #mod_ident { + use super::#target_ident; + #builder + #data + } + ) + } else { + quote!( + #target + #builder + #data + ) + } + } +} + +mod util { + use either::Either; + use proc_macro2::TokenStream; + use quote::{quote, ToTokens}; + + use crate::info; + /// 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_all_valued(value: bool, fields: &info::FieldCollection, generics: &syn::Generics) -> TokenStream { + let mut all = fields + .iter() + .filter_map(|field| match field.kind() { + info::FieldKind::Skipped | info::FieldKind::Optional => None, + info::FieldKind::Mandatory | info::FieldKind::Grouped => Some(Either::Right( + syn::LitBool::new(value, field.ident().span()), + )), + }); + add_const_valued_generics_for_type(&mut all, generics) + } + + /// 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. + pub fn add_const_valued_generics_for_type( + constants: &mut dyn Iterator>, + generics: &syn::Generics, + ) -> TokenStream { + let type_generics = generics.params.iter().map(|param| match param { + syn::GenericParam::Lifetime(lt) => <.lifetime.ident, + syn::GenericParam::Type(ty) => &ty.ident, + syn::GenericParam::Const(cnst) => &cnst.ident, + }); + + let tokens = constants.map(|constant| { + constant + .map_either(|iden| iden.to_token_stream(), |lit| lit.to_token_stream()) + .into_inner() + }); + quote!(< #(#type_generics,)* #(#tokens),* >) } } diff --git a/const_typed_builder_derive/src/generator/target_generator.rs b/const_typed_builder_derive/src/generator/target_generator.rs index f9a8d9d..a5e6f00 100644 --- a/const_typed_builder_derive/src/generator/target_generator.rs +++ b/const_typed_builder_derive/src/generator/target_generator.rs @@ -1,16 +1,15 @@ -use super::generics_generator::GenericsGenerator; +use super::util; +use crate::info; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; /// 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, - builder_name: &'a syn::Ident, +/// of the builder pattern based on the provided `info::Container`. +pub struct TargetGenerator<'info> { + info: &'info info::Container<'info>, } -impl<'a> TargetGenerator<'a> { +impl<'info> TargetGenerator<'info> { /// Creates a new `TargetGenerator` instance for code generation. /// /// # Arguments @@ -22,16 +21,8 @@ impl<'a> TargetGenerator<'a> { /// # Returns /// /// A `TargetGenerator` instance initialized with the provided information. - pub fn new( - generics_gen: GenericsGenerator<'a>, - target_name: &'a syn::Ident, - builder_name: &'a syn::Ident, - ) -> Self { - Self { - generics_gen, - target_name, - builder_name, - } + pub fn new(info: &'info info::Container<'info>) -> Self { + Self { info } } /// Generates the target struct's builder implementation code and returns a token stream. @@ -45,16 +36,23 @@ impl<'a> TargetGenerator<'a> { /// 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; - let const_generics = self.generics_gen.const_generics_valued(false); - let (impl_generics, type_generics, where_clause) = - self.generics_gen.target_generics().split_for_impl(); + let target_ident = self.info.ident(); + let builder_ident = self.info.builder_ident(); + + let builder_impl = if self.info.generate_module() { + let mod_ident = self.info.mod_ident(); + quote!(#mod_ident::#builder_ident) + } else { + builder_ident.to_token_stream() + }; + + let const_generics = util::const_generics_all_valued(false, self.info.field_collection(), self.info.generics()); + let (impl_generics, type_generics, where_clause) = self.info.generics().split_for_impl(); - let documentation = format!("Creates an instance of [`{}`]", self.builder_name); + let documentation = format!("Creates an instance of [`{}`]", self.info.builder_ident()); quote! { - impl #impl_generics Builder for #target_name #type_generics #where_clause { - type BuilderImpl = #builder_name #const_generics; + impl #impl_generics Builder for #target_ident #type_generics #where_clause { + type BuilderImpl = #builder_impl #const_generics; #[doc = #documentation] fn builder() -> Self::BuilderImpl { diff --git a/const_typed_builder_derive/src/info/container.rs b/const_typed_builder_derive/src/info/container.rs index 6b3bf5c..0362f5c 100644 --- a/const_typed_builder_derive/src/info/container.rs +++ b/const_typed_builder_derive/src/info/container.rs @@ -1,6 +1,6 @@ use super::field::FieldCollection; use super::group::GroupCollection; - +use convert_case::{Case, Casing}; use quote::format_ident; #[derive(Debug, Clone, Copy)] @@ -18,10 +18,6 @@ pub struct Container<'a> { 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: GroupCollection, /// A collection of `FieldInfo` instances representing struct fields. @@ -52,8 +48,6 @@ impl<'a> Container<'a> { ident, vis, generics, - builder_ident: format_ident!("{}{}", ident, "Builder"), - data_ident: format_ident!("{}{}", ident, "Data"), groups: group_collection, field_collection, solver_kind, @@ -61,7 +55,7 @@ impl<'a> Container<'a> { } /// Retrieves the identifier of the struct. - pub fn name(&self) -> &syn::Ident { + pub fn ident(&self) -> &syn::Ident { self.ident } @@ -76,13 +70,13 @@ impl<'a> Container<'a> { } /// Retrieves the identifier of the generated builder struct. - pub fn builder_name(&self) -> &syn::Ident { - &self.builder_ident + pub fn builder_ident(&self) -> syn::Ident { + format_ident!("{}{}", self.ident, "Builder") } /// Retrieves the identifier of the generated data struct. - pub fn data_name(&self) -> &syn::Ident { - &self.data_ident + pub fn data_ident(&self) -> syn::Ident { + format_ident!("{}{}", self.ident, "Data") } /// Retrieves a reference to the collection of `FieldInfo` instances representing struct fields. @@ -99,4 +93,16 @@ impl<'a> Container<'a> { pub fn solve_type(&self) -> SolverKind { self.solver_kind } + + pub fn data_field_ident(&self) -> syn::Ident { + format_ident!("__{}", self.data_ident().to_string().to_case(Case::Snake)) + } + + pub fn mod_ident(&self) -> syn::Ident { + format_ident!("{}", self.builder_ident().to_string().to_case(Case::Snake)) + } + + pub fn generate_module(&self) -> bool { + false + } } diff --git a/const_typed_builder_derive/src/info/group.rs b/const_typed_builder_derive/src/info/group.rs index d73773e..393faa8 100644 --- a/const_typed_builder_derive/src/info/group.rs +++ b/const_typed_builder_derive/src/info/group.rs @@ -1,6 +1,5 @@ -use proc_macro_error::{emit_error, emit_warning}; - use crate::symbol::{Symbol, AT_LEAST, AT_MOST, EXACT}; +use proc_macro_error::{emit_error, emit_warning}; use std::{ cmp::Ordering, collections::{BTreeSet, HashMap}, @@ -12,7 +11,7 @@ pub type GroupCollection = HashMap; /// Represents information about a group, including its name, member count, and group type. #[derive(Debug, Clone)] pub struct Group { - name: syn::Ident, + ident: syn::Ident, associated_indices: BTreeSet, group_type: GroupType, } @@ -30,15 +29,15 @@ impl Group { /// A `GroupInfo` instance with the provided name and group type. pub fn new(name: syn::Ident, group_type: GroupType) -> Self { Group { - name, + ident: name, associated_indices: BTreeSet::new(), group_type, } } /// Retrieves the name of the group. - pub fn name(&self) -> &syn::Ident { - &self.name + pub fn ident(&self) -> &syn::Ident { + &self.ident } /// Retrieves the expected member count based on the group type. @@ -90,10 +89,10 @@ impl Group { 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")) + emit_warning!(self.ident, format!("There is not an valid expected count")) } else if !valid_range.contains(&self.expected_count()) { emit_warning!( - self.name, + self.ident, format!("Expected count is outside of valid range {valid_range:#?}") ); } @@ -101,7 +100,7 @@ impl Group { GroupType::Exact(expected) => { match expected.cmp(&valid_range.start) { Ordering::Less => emit_error!( - self.name, + self.ident, "This group prevents all of the fields to be initialized"; hint = "Remove the group and use [builder(skip)] instead" ), @@ -110,12 +109,12 @@ impl Group { match expected.cmp(&valid_range.end) { Ordering::Less => {} Ordering::Equal => emit_warning!( - self.name, + self.ident, "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.ident, "Group can never be satisfied"; note = format!("Expected amount of fields: exact {}, amount of available fields: {}", expected, valid_range.end)), } @@ -123,7 +122,7 @@ impl Group { GroupType::AtLeast(expected) => { match expected.cmp(&valid_range.start) { Ordering::Less => emit_warning!( - self.name, + self.ident, "Group has no effect"; hint = "Consider removing the group" ), @@ -132,12 +131,12 @@ impl Group { match expected.cmp(&valid_range.end) { Ordering::Less => {} Ordering::Equal => emit_warning!( - self.name, + self.ident, "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.ident, "Group can never be satisfied"; note = format!("Expected amount of fields: at least {}, amount of available fields: {}", expected, valid_range.end); ), @@ -146,7 +145,7 @@ impl Group { GroupType::AtMost(expected) => { match expected.cmp(&valid_range.start) { Ordering::Less => emit_error!( - self.name, + self.ident, "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) @@ -156,7 +155,7 @@ impl Group { match expected.cmp(&valid_range.end) { Ordering::Less => {} Ordering::Equal | Ordering::Greater => emit_warning!( - self.name, + self.ident, "Group has no effect"; hint = "Consider removing the group" ), @@ -170,13 +169,13 @@ impl Eq for Group {} impl PartialEq for Group { fn eq(&self, other: &Self) -> bool { - self.name == other.name + self.ident == other.ident } } impl Hash for Group { fn hash(&self, state: &mut H) { - self.name.to_string().hash(state); + self.ident.to_string().hash(state); } } diff --git a/const_typed_builder_derive/src/parser/group.rs b/const_typed_builder_derive/src/parser/group.rs index 4e9ef17..7748245 100644 --- a/const_typed_builder_derive/src/parser/group.rs +++ b/const_typed_builder_derive/src/parser/group.rs @@ -38,7 +38,7 @@ impl<'a> Group<'a> { if let Some(group_type) = group_type { if let Some(earlier_definition) = self.groups.insert(group_name.to_string(), info::Group::new(group_name.clone(), group_type)) { - let earlier_span = earlier_definition.name().span(); + let earlier_span = earlier_definition.ident().span(); emit_error!( &group_name, "Group defined multiple times"; help = earlier_span => "Also defined here" diff --git a/const_typed_builder_test/Cargo.toml b/const_typed_builder_test/Cargo.toml index 203f16d..53897c1 100644 --- a/const_typed_builder_test/Cargo.toml +++ b/const_typed_builder_test/Cargo.toml @@ -13,6 +13,6 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dev-dependencies] +[dependencies] const_typed_builder = { path = "../../const_typed_builder", version = "=0.3.0" } trybuild = "1.0.84" \ No newline at end of file From 25433a39aef205a1e782da3eb3adb21b7404ce3b Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:26:17 +0200 Subject: [PATCH 05/15] Add TrackedField Also change function names in info::Container --- .../src/generator/builder_generator.rs | 116 +++++++++--------- .../src/generator/mod.rs | 39 +++--- .../src/info/container.rs | 4 +- const_typed_builder_derive/src/info/field.rs | 42 ++++++- const_typed_builder_derive/src/info/mod.rs | 2 +- 5 files changed, 122 insertions(+), 81 deletions(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index 7001a7d..b2dc786 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -1,10 +1,9 @@ use super::util; use crate::info; -use either::Either; use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; use quote::quote; -use std::collections::BTreeSet; +use std::{collections::BTreeSet, ops::Deref}; use syn::{parse_quote, GenericParam}; pub struct BuilderGenerator<'info> { @@ -12,6 +11,15 @@ pub struct BuilderGenerator<'info> { } impl<'info> BuilderGenerator<'info> { + /// Creates a new `BuilderGenerator` instance for code generation. + /// + /// # Arguments + /// + /// - `info`: The `info::Container` containing all the information of the data container. + /// + /// # Returns + /// + /// A `BuilderGenerator` instance initialized with the provided information. pub fn new(info: &'info info::Container) -> Self { Self { info } } @@ -75,7 +83,11 @@ impl<'info> BuilderGenerator<'info> { let data_ident = self.info.data_ident(); let data_field = self.info.data_field_ident(); - let type_generics = util::const_generics_all_valued(false, self.info.field_collection(), self.info.generics()); + let type_generics = util::const_generics_all_valued( + false, + self.info.field_collection(), + self.info.generics(), + ); let (impl_generics, _, where_clause) = self.info.generics().split_for_impl(); let documentation = format!("Creates a new [`{builder_ident}`] without any fields initialized"); @@ -110,7 +122,7 @@ impl<'info> BuilderGenerator<'info> { let (impl_generics, target_type_generics, where_clause) = self.info.generics().split_for_impl(); - match self.info.solve_type() { + match self.info.solver_kind() { info::SolverKind::BruteForce => { let build_impls = self.valid_groupident_combinations().map(|group_indices| { let type_generics = self.const_generic_idents_build(&group_indices); @@ -173,8 +185,8 @@ impl<'info> BuilderGenerator<'info> { let where_clause = &self.info.generics().where_clause; let field_ident = field.ident(); - let input_type = self.set_impl_input_type(field); - let input_value = self.set_impl_input_value(field); + let input_type = self.impl_set_input_type(field); + let input_value = self.impl_set_input_value(field); let documentation = format!(r#" Setter for the [`{}::{field_ident}`] field. @@ -211,10 +223,8 @@ Setter for the [`{}::{field_ident}`] field. .info .field_collection() .iter() - .filter_map(|field| match field.kind() { - info::FieldKind::Skipped | info::FieldKind::Optional => None, - info::FieldKind::Mandatory | info::FieldKind::Grouped => Some(field.const_ident()), - }); + .filter_map(info::TrackedField::new) + .map(|field| field.const_ident()); self.add_const_generics_for_impl(&mut all) } @@ -223,19 +233,13 @@ Setter for the [`{}::{field_ident}`] field. .info .field_collection() .iter() - .filter_map(|field| match field.kind() { - info::FieldKind::Skipped | info::FieldKind::Optional => None, - info::FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( - true, - proc_macro2::Span::call_site(), - ))), - info::FieldKind::Grouped if true_indices.contains(&field.index()) => Some( - Either::Right(syn::LitBool::new(true, proc_macro2::Span::call_site())), - ), - info::FieldKind::Grouped => Some(Either::Right(syn::LitBool::new( - false, - proc_macro2::Span::call_site(), - ))), + .filter_map(info::TrackedField::new) + .map(|field| match field.kind() { + info::TrackedFieldKind::Mandatory => quote!(true), + info::TrackedFieldKind::Grouped if true_indices.contains(&field.index()) => { + quote!(true) + } + info::TrackedFieldKind::Grouped => quote!(false), }); util::add_const_valued_generics_for_type(&mut all, self.info.generics()) } @@ -245,10 +249,13 @@ Setter for the [`{}::{field_ident}`] field. .info .field_collection() .iter() - .filter_map(|field| match field.kind() { - info::FieldKind::Skipped | info::FieldKind::Optional => None, - _ if field == field_info => None, - info::FieldKind::Mandatory | info::FieldKind::Grouped => Some(field.const_ident()), + .filter_map(info::TrackedField::new) + .filter_map(|field| { + if field.deref() == field_info { + None + } else { + Some(field.const_ident()) + } }); self.add_const_generics_for_impl(&mut all) } @@ -258,14 +265,13 @@ Setter for the [`{}::{field_ident}`] field. .info .field_collection() .iter() - .filter_map(|field| match field.kind() { - info::FieldKind::Skipped | info::FieldKind::Optional => None, - _ if field == field_info => Some(Either::Right(syn::LitBool::new( - value, - field_info.ident().span(), - ))), - info::FieldKind::Mandatory | info::FieldKind::Grouped => { - Some(Either::Left(field.const_ident())) + .filter_map(info::TrackedField::new) + .map(|field| { + if field.deref() == field_info { + quote!(#value) + } else { + let ident = field.ident(); + quote!(#ident) } }); util::add_const_valued_generics_for_type(&mut all, self.info.generics()) @@ -277,10 +283,10 @@ Setter for the [`{}::{field_ident}`] field. .field_collection() .iter() .filter_map(|field| match field.kind() { - info::FieldKind::Skipped - | info::FieldKind::Optional - | info::FieldKind::Mandatory => None, info::FieldKind::Grouped => Some(field.const_ident()), + info::FieldKind::Optional + | info::FieldKind::Skipped + | info::FieldKind::Mandatory => None, }); self.add_const_generics_for_impl(&mut all) } @@ -290,23 +296,23 @@ Setter for the [`{}::{field_ident}`] field. .info .field_collection() .iter() - .filter_map(|field| match field.kind() { - info::FieldKind::Skipped | info::FieldKind::Optional => None, - info::FieldKind::Mandatory => Some(Either::Right(syn::LitBool::new( - true, - proc_macro2::Span::call_site(), - ))), - info::FieldKind::Grouped => Some(Either::Left(field.const_ident())), + .filter_map(info::TrackedField::new) + .map(|field| match field.kind() { + info::TrackedFieldKind::Mandatory => quote!(true), + info::TrackedFieldKind::Grouped => { + let ident = field.const_ident(); + quote!(#ident) + } }); util::add_const_valued_generics_for_type(&mut all, self.info.generics()) } fn impl_correctness_verifier(&self) -> TokenStream { - if self.info.groups().is_empty() { + if self.info.group_collection().is_empty() { return TokenStream::new(); } - let all = self.info.groups().values().map(|group| { + let all = self.info.group_collection().values().map(|group| { let partials = group.indices().iter().map(|index| self.info.field_collection().get(*index).expect("Could not find field associated to group").const_ident()); let function_call: syn::Ident = group.function_symbol().into(); let count = group.expected_count(); @@ -329,7 +335,7 @@ Setter for the [`{}::{field_ident}`] field. } fn impl_correctness_check(&self) -> TokenStream { - if self.info.groups().is_empty() { + if self.info.group_collection().is_empty() { TokenStream::new() } else { quote!(let _ = Self::GROUP_VERIFIER;) @@ -337,7 +343,7 @@ Setter for the [`{}::{field_ident}`] field. } fn impl_correctness_helper_fns(&self) -> TokenStream { - if self.info.groups().is_empty() { + if self.info.group_collection().is_empty() { return TokenStream::new(); } @@ -345,7 +351,7 @@ Setter for the [`{}::{field_ident}`] field. let mut at_least = false; let mut at_most = false; - for group in self.info.groups().values() { + for group in self.info.group_collection().values() { match group.group_type() { info::GroupType::Exact(_) => exact = true, info::GroupType::AtLeast(_) => at_least = true, @@ -411,7 +417,7 @@ Setter for the [`{}::{field_ident}`] field. ) } - fn set_impl_input_type(&self, field: &'info info::Field) -> Option { + fn impl_set_input_type(&self, field: &'info info::Field) -> Option { if field.kind() == &info::FieldKind::Skipped { return None; } @@ -432,7 +438,7 @@ Setter for the [`{}::{field_ident}`] field. Some(quote!(#field_ident: #field_ty)) } - fn set_impl_input_value(&self, field: &'info info::Field) -> Option { + fn impl_set_input_value(&self, field: &'info info::Field) -> Option { if field.kind() == &info::FieldKind::Skipped { return None; } @@ -461,7 +467,7 @@ Setter for the [`{}::{field_ident}`] field. fn valid_groupident_combinations(&self) -> impl Iterator> + '_ { let group_indices: BTreeSet = self .info - .groups() + .group_collection() .values() .flat_map(|group| group.indices().clone()) .collect(); @@ -470,7 +476,7 @@ Setter for the [`{}::{field_ident}`] field. powerset.filter_map(|set| { if self .info - .groups() + .group_collection() .values() .all(|group| group.is_valid_with(&set)) { @@ -488,7 +494,7 @@ Setter for the [`{}::{field_ident}`] field. /// A `syn::Generics` instance representing the generics for the builder struct. fn add_const_generics_for_impl( &self, - tokens: &mut dyn Iterator, + tokens: &mut impl Iterator, ) -> syn::Generics { let mut res = self.info.generics().clone(); res.params diff --git a/const_typed_builder_derive/src/generator/mod.rs b/const_typed_builder_derive/src/generator/mod.rs index ca9392c..f00476a 100644 --- a/const_typed_builder_derive/src/generator/mod.rs +++ b/const_typed_builder_derive/src/generator/mod.rs @@ -6,7 +6,7 @@ use self::{ builder_generator::BuilderGenerator, data_generator::DataGenerator, target_generator::TargetGenerator, }; -use crate::info::{Container, self}; +use crate::info; use proc_macro2::TokenStream; use quote::quote; @@ -23,13 +23,15 @@ impl<'info> Generator<'info> { /// /// # Arguments /// - /// - `info`: A reference to the `StructInfo` representing the input struct. + /// - `info`: The `info::Container` containing all the information of the data container. /// /// # Returns /// /// A `Generator` instance initialized with the provided `StructInfo`. - pub fn new(info: &'info Container<'info>) -> Self { - info.groups().values().for_each(|group| group.check()); + pub fn new(info: &'info info::Container<'info>) -> Self { + info.group_collection() + .values() + .for_each(|group| group.check()); Generator { info, @@ -71,9 +73,8 @@ impl<'info> Generator<'info> { } mod util { - use either::Either; use proc_macro2::TokenStream; - use quote::{quote, ToTokens}; + use quote::quote; use crate::info; /// Generates const generics with boolean values and returns a token stream. @@ -85,15 +86,15 @@ mod util { /// # Returns /// /// A `TokenStream` representing the generated const generics. - pub fn const_generics_all_valued(value: bool, fields: &info::FieldCollection, generics: &syn::Generics) -> TokenStream { + pub fn const_generics_all_valued( + value: bool, + fields: &info::FieldCollection, + generics: &syn::Generics, + ) -> TokenStream { let mut all = fields .iter() - .filter_map(|field| match field.kind() { - info::FieldKind::Skipped | info::FieldKind::Optional => None, - info::FieldKind::Mandatory | info::FieldKind::Grouped => Some(Either::Right( - syn::LitBool::new(value, field.ident().span()), - )), - }); + .filter_map(info::TrackedField::new) + .map(|_| quote!(#value)); add_const_valued_generics_for_type(&mut all, generics) } @@ -103,20 +104,16 @@ mod util { /// /// A `Tokenstream` instance representing the generics for the builder struct. pub fn add_const_valued_generics_for_type( - constants: &mut dyn Iterator>, + constants: &mut impl Iterator, generics: &syn::Generics, ) -> TokenStream { - let type_generics = generics.params.iter().map(|param| match param { + dbg!(generics.type_params().collect::>()); + let generic_idents = generics.params.iter().map(|param| match param { syn::GenericParam::Lifetime(lt) => <.lifetime.ident, syn::GenericParam::Type(ty) => &ty.ident, syn::GenericParam::Const(cnst) => &cnst.ident, }); - let tokens = constants.map(|constant| { - constant - .map_either(|iden| iden.to_token_stream(), |lit| lit.to_token_stream()) - .into_inner() - }); - quote!(< #(#type_generics,)* #(#tokens),* >) + quote!(< #(#generic_idents,)* #(#constants),* >) } } diff --git a/const_typed_builder_derive/src/info/container.rs b/const_typed_builder_derive/src/info/container.rs index 0362f5c..939f8bb 100644 --- a/const_typed_builder_derive/src/info/container.rs +++ b/const_typed_builder_derive/src/info/container.rs @@ -85,12 +85,12 @@ impl<'a> Container<'a> { } /// Retrieves a reference to the map of group names to their respective `GroupInfo`. - pub fn groups(&self) -> &GroupCollection { + pub fn group_collection(&self) -> &GroupCollection { &self.groups } /// Retrieves the solver type used to find all possible valid combinations for the groups - pub fn solve_type(&self) -> SolverKind { + pub fn solver_kind(&self) -> SolverKind { self.solver_kind } diff --git a/const_typed_builder_derive/src/info/field.rs b/const_typed_builder_derive/src/info/field.rs index d7ae0b3..b9cf764 100644 --- a/const_typed_builder_derive/src/info/field.rs +++ b/const_typed_builder_derive/src/info/field.rs @@ -3,11 +3,12 @@ use crate::{ CONST_IDENT_PREFIX, }; use quote::format_ident; +use std::ops::Deref; /// A type alias for a collection of `FieldInfo` instances. pub type FieldCollection<'a> = Vec>; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq)] pub enum FieldKind { Optional, Skipped, @@ -78,7 +79,7 @@ impl<'a> Field<'a> { inner_type(self.ty) } - /// Retrieves the kind of the field, which can be Optional, Mandatory, or Grouped. + /// Retrieves the kind of the field, which can be Optional, Mandatory, Skipped or Grouped. pub fn kind(&self) -> &FieldKind { &self.kind } @@ -122,3 +123,40 @@ impl<'a> Ord for Field<'a> { self.index.cmp(&other.index) } } +pub enum TrackedFieldKind { + Mandatory, + Grouped, +} +pub struct TrackedField<'a> { + field: &'a Field<'a>, + kind: TrackedFieldKind, +} + +impl<'a> TrackedField<'a> { + /// Creates a [`TrackedField`] if the input [`Field`] is Mandatory or Grouped. + pub fn new(field: &'a Field) -> Option { + match field.kind() { + FieldKind::Optional | FieldKind::Skipped => None, + FieldKind::Mandatory => Some(Self { + field, + kind: TrackedFieldKind::Mandatory, + }), + FieldKind::Grouped => Some(Self { + field, + kind: TrackedFieldKind::Grouped, + }), + } + } + /// Retrieves the kind of the field, which can be Mandatory, or Grouped. + pub fn kind(&self) -> &TrackedFieldKind { + &self.kind + } +} + +impl<'a> Deref for TrackedField<'a> { + type Target = Field<'a>; + + fn deref(&self) -> &Self::Target { + self.field + } +} diff --git a/const_typed_builder_derive/src/info/mod.rs b/const_typed_builder_derive/src/info/mod.rs index af57a40..9be9be4 100644 --- a/const_typed_builder_derive/src/info/mod.rs +++ b/const_typed_builder_derive/src/info/mod.rs @@ -3,5 +3,5 @@ mod field; mod group; pub use container::{Container, SolverKind}; -pub use field::{Field, FieldCollection, FieldKind}; +pub use field::{Field, FieldCollection, FieldKind, TrackedField, TrackedFieldKind}; pub use group::{Group, GroupCollection, GroupType}; From ec5cfa9b74b61aa2a4b3bd104ed666fe41a93e19 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:28:09 +0200 Subject: [PATCH 06/15] Change some of the documentation --- .../src/generator/data_generator.rs | 9 +++------ .../src/generator/target_generator.rs | 10 ++++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/const_typed_builder_derive/src/generator/data_generator.rs b/const_typed_builder_derive/src/generator/data_generator.rs index f5aa137..0bf2f3a 100644 --- a/const_typed_builder_derive/src/generator/data_generator.rs +++ b/const_typed_builder_derive/src/generator/data_generator.rs @@ -4,7 +4,7 @@ use quote::{quote, ToTokens}; /// 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> { +pub struct DataGenerator<'a> { info: &'a info::Container<'a>, } @@ -13,15 +13,12 @@ impl<'a> DataGenerator<'a> { /// /// # Arguments /// - /// - `field_gen`: The `FieldGenerator` responsible for generating field-related code. - /// - `generics_gen`: The `GenericsGenerator` responsible for generating generics information. - /// - `target_ident`: A reference to the identifier representing the target struct's ident. - /// - `data_ident`: A reference to the identifier representing the data struct's ident. + /// - `info`: The `info::Container` containing all the information of the data container. /// /// # Returns /// /// A `DataGenerator` instance initialized with the provided information. - pub(super) fn new(info: &'a info::Container<'a>) -> Self { + pub fn new(info: &'a info::Container<'a>) -> Self { Self { info } } diff --git a/const_typed_builder_derive/src/generator/target_generator.rs b/const_typed_builder_derive/src/generator/target_generator.rs index a5e6f00..39dae3f 100644 --- a/const_typed_builder_derive/src/generator/target_generator.rs +++ b/const_typed_builder_derive/src/generator/target_generator.rs @@ -14,9 +14,7 @@ impl<'info> TargetGenerator<'info> { /// /// # 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. + /// - `info`: The `info::Container` containing all the information of the data container. /// /// # Returns /// @@ -46,7 +44,11 @@ impl<'info> TargetGenerator<'info> { builder_ident.to_token_stream() }; - let const_generics = util::const_generics_all_valued(false, self.info.field_collection(), self.info.generics()); + let const_generics = util::const_generics_all_valued( + false, + self.info.field_collection(), + self.info.generics(), + ); let (impl_generics, type_generics, where_clause) = self.info.generics().split_for_impl(); let documentation = format!("Creates an instance of [`{}`]", self.info.builder_ident()); From fa6537dbf1439c90d67ae62cdc01ef75c63a9c56 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:32:58 +0200 Subject: [PATCH 07/15] Fix typo: `ident()` -> `const_ident()` --- 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 b2dc786..b481126 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -270,7 +270,7 @@ Setter for the [`{}::{field_ident}`] field. if field.deref() == field_info { quote!(#value) } else { - let ident = field.ident(); + let ident = field.const_ident(); quote!(#ident) } }); From aed1f7513f076d9126f5c8dec2f93796a1dc41f2 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:33:28 +0200 Subject: [PATCH 08/15] Change value and location of `CONST_IDENT_PREFIX` --- const_typed_builder_derive/src/info/field.rs | 7 +++---- const_typed_builder_derive/src/lib.rs | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/const_typed_builder_derive/src/info/field.rs b/const_typed_builder_derive/src/info/field.rs index b9cf764..596614f 100644 --- a/const_typed_builder_derive/src/info/field.rs +++ b/const_typed_builder_derive/src/info/field.rs @@ -1,13 +1,12 @@ -use crate::{ - util::{inner_type, is_option}, - CONST_IDENT_PREFIX, -}; +use crate::util::{inner_type, is_option}; use quote::format_ident; use std::ops::Deref; /// A type alias for a collection of `FieldInfo` instances. pub type FieldCollection<'a> = Vec>; +const CONST_IDENT_PREFIX: &str = "__BUILDER_CONST_"; + #[derive(Debug, PartialEq, Eq)] pub enum FieldKind { Optional, diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index 457b2a5..edb061c 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -10,8 +10,6 @@ use proc_macro_error::proc_macro_error; use quote::quote; use syn::DeriveInput; -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 /// pattern implementation for that struct. From 38fe2f4b24ce28673e06d0815fef90b3b23ab38a Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:41:48 +0200 Subject: [PATCH 09/15] change add `Parser` suffix to parser structs --- const_typed_builder_derive/src/lib.rs | 2 +- .../src/parser/container.rs | 41 ++++++----- .../src/parser/field.rs | 70 +++++++++---------- .../src/parser/group.rs | 27 +++---- const_typed_builder_derive/src/parser/mod.rs | 6 +- 5 files changed, 76 insertions(+), 70 deletions(-) diff --git a/const_typed_builder_derive/src/lib.rs b/const_typed_builder_derive/src/lib.rs index edb061c..63966a1 100644 --- a/const_typed_builder_derive/src/lib.rs +++ b/const_typed_builder_derive/src/lib.rs @@ -49,7 +49,7 @@ pub fn derive_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream /// /// An optional `TokenStream` representing the generated token stream. fn impl_my_derive(ast: &syn::DeriveInput) -> Option { - let container_info = parser::Container::new().parse(ast)?; + let container_info = parser::ContainerParser::new().parse(ast)?; let generator = Generator::new(&container_info); Some(generator.generate()) } diff --git a/const_typed_builder_derive/src/parser/container.rs b/const_typed_builder_derive/src/parser/container.rs index 28710ac..eae3e49 100644 --- a/const_typed_builder_derive/src/parser/container.rs +++ b/const_typed_builder_derive/src/parser/container.rs @@ -1,18 +1,21 @@ -use super::{Field, Group}; -use crate::{info, symbol}; +use super::{FieldParser, GroupParser}; +use crate::{ + info::{Container, Field, FieldCollection, GroupCollection, SolverKind}, + symbol, +}; use proc_macro_error::{emit_call_site_error, emit_error, emit_warning}; /// Represents the parser for struct generation. #[derive(Debug)] -pub struct Container { +pub struct ContainerParser { assume_mandatory: bool, /// A map of group names to their respective `GroupInfo`. - groups: info::GroupCollection, + groups: GroupCollection, /// The solver used to find all possible valid combinations for the groups - solver_kind: info::SolverKind, + solver_kind: SolverKind, } -impl Container { +impl ContainerParser { pub fn new() -> Self { Self::default() } @@ -25,7 +28,7 @@ impl Container { /// # Returns /// /// A `syn::Result` indicating success or failure of attribute handling. - pub fn parse(mut self, ast: &syn::DeriveInput) -> Option { + pub fn parse(mut self, ast: &syn::DeriveInput) -> Option { let syn::DeriveInput { attrs, vis, @@ -38,7 +41,7 @@ impl Container { let fields = self.handle_data(data)?; - Some(info::Container::new( + Some(Container::new( vis, generics, ident, @@ -79,7 +82,7 @@ impl Container { ), } match (&attr_ident.to_string()).into() { - symbol::GROUP => Group::new(&mut self.groups).parse(attr), + symbol::GROUP => GroupParser::new(&mut self.groups).parse(attr), symbol::BUILDER => self.handle_attribute_builder(attr), _ => emit_error!(&attr, "Unknown attribute"), } @@ -120,8 +123,8 @@ impl Container { symbol::SOLVER => { let syn::ExprPath { path, .. } = meta.value()?.parse()?; match (&path.require_ident()?.to_string()).into() { - symbol::BRUTE_FORCE => self.solver_kind = info::SolverKind::BruteForce, - symbol::COMPILER => self.solver_kind = info::SolverKind::Compiler, + symbol::BRUTE_FORCE => self.solver_kind = SolverKind::BruteForce, + symbol::COMPILER => self.solver_kind = SolverKind::Compiler, _ => emit_error!(&path, "Unknown solver type"), } } @@ -139,7 +142,7 @@ impl Container { }) } - fn handle_data<'a>(&mut self, data: &'a syn::Data) -> Option> { + fn handle_data<'a>(&mut self, data: &'a syn::Data) -> Option> { match data { syn::Data::Struct(syn::DataStruct { fields, .. }) => self.handle_fields(fields), syn::Data::Enum(syn::DataEnum { variants, .. }) => { @@ -156,7 +159,7 @@ impl Container { } } - fn handle_fields<'a>(&mut self, fields: &'a syn::Fields) -> Option>> { + fn handle_fields<'a>(&mut self, fields: &'a syn::Fields) -> Option>> { match fields { syn::Fields::Named(fields) => Some(self.handle_named_fields(fields)), syn::Fields::Unnamed(fields) => { @@ -167,7 +170,7 @@ impl Container { } } - fn handle_named_fields<'a>(&mut self, fields: &'a syn::FieldsNamed) -> Vec> { + fn handle_named_fields<'a>(&mut self, fields: &'a syn::FieldsNamed) -> Vec> { fields .named .iter() @@ -177,18 +180,18 @@ impl Container { .ident .as_ref() .expect("FieldsNamed should have an ident"); - Field::new(index, self.assume_mandatory, &mut self.groups).parse(ident, field) + FieldParser::new(index, self.assume_mandatory, &mut self.groups).parse(ident, field) }) .collect::>() } } -impl Default for Container { +impl Default for ContainerParser { fn default() -> Self { - Container { + ContainerParser { assume_mandatory: false, - groups: info::GroupCollection::new(), - solver_kind: info::SolverKind::BruteForce, + groups: GroupCollection::new(), + solver_kind: SolverKind::BruteForce, } } } diff --git a/const_typed_builder_derive/src/parser/field.rs b/const_typed_builder_derive/src/parser/field.rs index f1da9dc..7ac6513 100644 --- a/const_typed_builder_derive/src/parser/field.rs +++ b/const_typed_builder_derive/src/parser/field.rs @@ -1,21 +1,25 @@ -use crate::{info, symbol, util::is_option}; +use crate::{ + info::{Field, FieldKind, GroupCollection}, + symbol, + util::is_option, +}; use proc_macro_error::{emit_error, emit_warning}; /// Represents settings for struct field generation. #[derive(Debug)] -pub struct Field<'parser> { - kind: Option, +pub struct FieldParser<'parser> { + kind: Option, propagate: bool, index: usize, assume_mandatory: bool, - group_collection: &'parser mut info::GroupCollection, + group_collection: &'parser mut GroupCollection, } -impl<'parser> Field<'parser> { +impl<'parser> FieldParser<'parser> { pub fn new( index: usize, assume_mandatory: bool, - group_collection: &'parser mut info::GroupCollection, + group_collection: &'parser mut GroupCollection, ) -> Self { Self { kind: None, @@ -26,15 +30,11 @@ impl<'parser> Field<'parser> { } } - pub fn parse<'ast>( - mut self, - ident: &'ast syn::Ident, - field: &'ast syn::Field, - ) -> info::Field<'ast> { + pub fn parse<'ast>(mut self, ident: &'ast syn::Ident, field: &'ast syn::Field) -> Field<'ast> { let syn::Field { ty, attrs, .. } = field; if !is_option(ty) { - self.kind = Some(info::FieldKind::Mandatory); // If its not an option type it MUST always be mandatory + self.kind = Some(FieldKind::Mandatory); // If its not an option type it MUST always be mandatory } attrs @@ -43,13 +43,13 @@ impl<'parser> Field<'parser> { if self.kind.is_none() { self.kind = if self.assume_mandatory { - Some(info::FieldKind::Mandatory) + Some(FieldKind::Mandatory) } else { - Some(info::FieldKind::Optional) + Some(FieldKind::Optional) } } - info::Field::new(ident, ty, self.index, self.kind.unwrap(), self.propagate) + Field::new(ident, ty, self.index, self.kind.unwrap(), self.propagate) } /// Handles the parsing and processing of a builder attribute applied to a field. @@ -140,19 +140,19 @@ impl<'parser> Field<'parser> { fn handle_attribute_skip(&mut self, ident: &syn::Ident) { match self.kind { - None => self.kind = Some(info::FieldKind::Skipped), - Some(info::FieldKind::Optional) => emit_error!( + None => self.kind = Some(FieldKind::Skipped), + Some(FieldKind::Optional) => emit_error!( ident, "Can't define field as skipped as its already defined as optional"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Skipped) => { + Some(FieldKind::Skipped) => { emit_warning!(ident, "Defined field as skipped multiple times") } - Some(info::FieldKind::Mandatory) => emit_error!( + Some(FieldKind::Mandatory) => emit_error!( ident, "Can't define field as skipped as its already defined as mandatory"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Grouped) => emit_error!( + Some(FieldKind::Grouped) => emit_error!( ident, "Can't define field as skipped when its also part of a group"; hint = "Remove either types of attribute from this field" ), @@ -161,19 +161,19 @@ impl<'parser> Field<'parser> { fn handle_attribute_mandatory(&mut self, ident: &syn::Ident) { match self.kind { - None => self.kind = Some(info::FieldKind::Mandatory), - Some(info::FieldKind::Optional) => emit_error!( + None => self.kind = Some(FieldKind::Mandatory), + Some(FieldKind::Optional) => emit_error!( ident, "Can't define field as mandatory as its already defined as optional"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Skipped) => emit_error!( + Some(FieldKind::Skipped) => emit_error!( ident, "Can't define field as mandatory as its already defined as skipped"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Mandatory) => { + Some(FieldKind::Mandatory) => { emit_warning!(ident, "Defined field as mandatory multiple times") } - Some(info::FieldKind::Grouped) => emit_error!( + Some(FieldKind::Grouped) => emit_error!( ident, "Can't define field as mandatory when its also part of a group"; hint = "Remove either types of attribute from this field" ), @@ -182,19 +182,19 @@ impl<'parser> Field<'parser> { fn handle_attribute_optional(&mut self, ident: &syn::Ident) { match self.kind { - None => self.kind = Some(info::FieldKind::Optional), - Some(info::FieldKind::Optional) => { + None => self.kind = Some(FieldKind::Optional), + Some(FieldKind::Optional) => { emit_warning!(ident, "Defined field as optional multiple times") } - Some(info::FieldKind::Skipped) => emit_error!( + Some(FieldKind::Skipped) => emit_error!( ident, "Can't define field as optional as its already defined as skipped"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Mandatory) => emit_error!( + Some(FieldKind::Mandatory) => emit_error!( ident, "Can't define field as optional as its already defined as mandatory"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Grouped) => emit_error!( + Some(FieldKind::Grouped) => emit_error!( ident, "Can't define field as optional when its also part of a group"; hint = "Remove either types of attribute from this field" ), @@ -203,20 +203,20 @@ impl<'parser> Field<'parser> { fn handle_attribute_group(&mut self, ident: &syn::Ident, meta: &syn::meta::ParseNestedMeta) { match self.kind { - None => self.kind = Some(info::FieldKind::Grouped), - Some(info::FieldKind::Optional) => emit_error!( + None => self.kind = Some(FieldKind::Grouped), + Some(FieldKind::Optional) => emit_error!( ident, "Can't define field as part of a group as its already defined as optional"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Skipped) => emit_error!( + Some(FieldKind::Skipped) => emit_error!( ident, "Can't define field as as part of a group as its already defined as skipped"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Mandatory) => emit_error!( + Some(FieldKind::Mandatory) => emit_error!( ident, "Can't define field as as part of a group as its already defined as mandatory"; hint = "Remove either types of attribute from this field" ), - Some(info::FieldKind::Grouped) => {} + Some(FieldKind::Grouped) => {} } match self.extract_group_name(meta) { Ok(group_name) => { diff --git a/const_typed_builder_derive/src/parser/group.rs b/const_typed_builder_derive/src/parser/group.rs index 7748245..9b49556 100644 --- a/const_typed_builder_derive/src/parser/group.rs +++ b/const_typed_builder_derive/src/parser/group.rs @@ -1,12 +1,15 @@ -use crate::{info, symbol}; +use crate::{ + info::{Group, GroupCollection, GroupType}, + symbol, +}; use proc_macro_error::emit_error; -pub struct Group<'a> { - groups: &'a mut info::GroupCollection, +pub struct GroupParser<'a> { + groups: &'a mut GroupCollection, } -impl<'a> Group<'a> { - pub fn new(groups: &'a mut info::GroupCollection) -> Self { +impl<'a> GroupParser<'a> { + pub fn new(groups: &'a mut GroupCollection) -> Self { Self { groups } } @@ -37,7 +40,7 @@ impl<'a> Group<'a> { }; if let Some(group_type) = group_type { - if let Some(earlier_definition) = self.groups.insert(group_name.to_string(), info::Group::new(group_name.clone(), group_type)) { + if let Some(earlier_definition) = self.groups.insert(group_name.to_string(), Group::new(group_name.clone(), group_type)) { let earlier_span = earlier_definition.ident().span(); emit_error!( &group_name, "Group defined multiple times"; @@ -54,7 +57,7 @@ impl<'a> Group<'a> { )); } - fn handle_group_call(&self, expr: &syn::ExprCall) -> Option { + fn handle_group_call(&self, expr: &syn::ExprCall) -> Option { let syn::ExprCall { func, args, .. } = expr; let type_ident = match func.as_ref() { @@ -100,9 +103,9 @@ impl<'a> Group<'a> { }; let group_type = match (&type_ident.to_string()).into() { - symbol::EXACT => info::GroupType::Exact(group_argument), - symbol::AT_LEAST => info::GroupType::AtLeast(group_argument), - symbol::AT_MOST => info::GroupType::AtMost(group_argument), + symbol::EXACT => GroupType::Exact(group_argument), + symbol::AT_LEAST => GroupType::AtLeast(group_argument), + symbol::AT_MOST => GroupType::AtMost(group_argument), symbol::SINGLE => { emit_error!( args, @@ -122,7 +125,7 @@ impl<'a> Group<'a> { Some(group_type) } - fn handle_group_path(&self, expr: &syn::ExprPath) -> Option { + fn handle_group_path(&self, expr: &syn::ExprPath) -> Option { let syn::ExprPath { path, .. } = expr; let type_ident = match path.require_ident() { Ok(ident) => ident, @@ -137,7 +140,7 @@ impl<'a> Group<'a> { }; match (&type_ident.to_string()).into() { - symbol::SINGLE => Some(info::GroupType::Exact(1)), + symbol::SINGLE => Some(GroupType::Exact(1)), symbol::EXACT | symbol::AT_LEAST | symbol::AT_MOST => { emit_error!( &expr, diff --git a/const_typed_builder_derive/src/parser/mod.rs b/const_typed_builder_derive/src/parser/mod.rs index f38baa0..a3ef402 100644 --- a/const_typed_builder_derive/src/parser/mod.rs +++ b/const_typed_builder_derive/src/parser/mod.rs @@ -2,6 +2,6 @@ mod container; mod field; mod group; -pub use container::Container; -use field::Field; -use group::Group; +pub use container::ContainerParser; +use field::FieldParser; +use group::GroupParser; From 4aff7b42a42d571beb18168837f4dd63c1c4bce4 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:43:02 +0200 Subject: [PATCH 10/15] import specifix structs instead of blanket `info` use statement --- .../src/generator/builder_generator.rs | 62 +++++++++---------- .../src/generator/data_generator.rs | 30 +++++---- .../src/generator/target_generator.rs | 10 +-- 3 files changed, 50 insertions(+), 52 deletions(-) diff --git a/const_typed_builder_derive/src/generator/builder_generator.rs b/const_typed_builder_derive/src/generator/builder_generator.rs index b481126..d3dffc9 100644 --- a/const_typed_builder_derive/src/generator/builder_generator.rs +++ b/const_typed_builder_derive/src/generator/builder_generator.rs @@ -1,5 +1,7 @@ use super::util; -use crate::info; +use crate::info::{ + Container, Field, FieldKind, GroupType, SolverKind, TrackedField, TrackedFieldKind, +}; use itertools::{Itertools, Powerset}; use proc_macro2::TokenStream; use quote::quote; @@ -7,7 +9,7 @@ use std::{collections::BTreeSet, ops::Deref}; use syn::{parse_quote, GenericParam}; pub struct BuilderGenerator<'info> { - info: &'info info::Container<'info>, + info: &'info Container<'info>, } impl<'info> BuilderGenerator<'info> { @@ -15,12 +17,12 @@ impl<'info> BuilderGenerator<'info> { /// /// # Arguments /// - /// - `info`: The `info::Container` containing all the information of the data container. + /// - `info`: The `Container` containing all the information of the data container. /// /// # Returns /// /// A `BuilderGenerator` instance initialized with the provided information. - pub fn new(info: &'info info::Container) -> Self { + pub fn new(info: &'info Container) -> Self { Self { info } } @@ -123,7 +125,7 @@ impl<'info> BuilderGenerator<'info> { self.info.generics().split_for_impl(); match self.info.solver_kind() { - info::SolverKind::BruteForce => { + SolverKind::BruteForce => { let build_impls = self.valid_groupident_combinations().map(|group_indices| { let type_generics = self.const_generic_idents_build(&group_indices); @@ -141,7 +143,7 @@ impl<'info> BuilderGenerator<'info> { #(#build_impls)* ) } - info::SolverKind::Compiler => { + SolverKind::Compiler => { let builder_ident = self.info.builder_ident(); let impl_generics = self.const_generic_group_partial_idents(); let type_generics = self.const_generic_idents_build_unset_group(); @@ -177,7 +179,7 @@ impl<'info> BuilderGenerator<'info> { .info .field_collection() .iter() - .filter(|field| field.kind() != &info::FieldKind::Skipped) + .filter(|field| field.kind() != &FieldKind::Skipped) .map(|field| { let const_idents_impl = self.const_generic_idents_set_impl(field); let const_idents_type_input = self.const_generic_idents_set_type(field, false); @@ -223,7 +225,7 @@ Setter for the [`{}::{field_ident}`] field. .info .field_collection() .iter() - .filter_map(info::TrackedField::new) + .filter_map(TrackedField::new) .map(|field| field.const_ident()); self.add_const_generics_for_impl(&mut all) } @@ -233,23 +235,23 @@ Setter for the [`{}::{field_ident}`] field. .info .field_collection() .iter() - .filter_map(info::TrackedField::new) + .filter_map(TrackedField::new) .map(|field| match field.kind() { - info::TrackedFieldKind::Mandatory => quote!(true), - info::TrackedFieldKind::Grouped if true_indices.contains(&field.index()) => { + TrackedFieldKind::Mandatory => quote!(true), + TrackedFieldKind::Grouped if true_indices.contains(&field.index()) => { quote!(true) } - info::TrackedFieldKind::Grouped => quote!(false), + TrackedFieldKind::Grouped => quote!(false), }); util::add_const_valued_generics_for_type(&mut all, self.info.generics()) } - fn const_generic_idents_set_impl(&self, field_info: &info::Field) -> syn::Generics { + fn const_generic_idents_set_impl(&self, field_info: &Field) -> syn::Generics { let mut all = self .info .field_collection() .iter() - .filter_map(info::TrackedField::new) + .filter_map(TrackedField::new) .filter_map(|field| { if field.deref() == field_info { None @@ -260,12 +262,12 @@ Setter for the [`{}::{field_ident}`] field. self.add_const_generics_for_impl(&mut all) } - fn const_generic_idents_set_type(&self, field_info: &info::Field, value: bool) -> TokenStream { + fn const_generic_idents_set_type(&self, field_info: &Field, value: bool) -> TokenStream { let mut all = self .info .field_collection() .iter() - .filter_map(info::TrackedField::new) + .filter_map(TrackedField::new) .map(|field| { if field.deref() == field_info { quote!(#value) @@ -283,10 +285,8 @@ Setter for the [`{}::{field_ident}`] field. .field_collection() .iter() .filter_map(|field| match field.kind() { - info::FieldKind::Grouped => Some(field.const_ident()), - info::FieldKind::Optional - | info::FieldKind::Skipped - | info::FieldKind::Mandatory => None, + FieldKind::Grouped => Some(field.const_ident()), + FieldKind::Optional | FieldKind::Skipped | FieldKind::Mandatory => None, }); self.add_const_generics_for_impl(&mut all) } @@ -296,10 +296,10 @@ Setter for the [`{}::{field_ident}`] field. .info .field_collection() .iter() - .filter_map(info::TrackedField::new) + .filter_map(TrackedField::new) .map(|field| match field.kind() { - info::TrackedFieldKind::Mandatory => quote!(true), - info::TrackedFieldKind::Grouped => { + TrackedFieldKind::Mandatory => quote!(true), + TrackedFieldKind::Grouped => { let ident = field.const_ident(); quote!(#ident) } @@ -353,9 +353,9 @@ Setter for the [`{}::{field_ident}`] field. for group in self.info.group_collection().values() { match group.group_type() { - info::GroupType::Exact(_) => exact = true, - info::GroupType::AtLeast(_) => at_least = true, - info::GroupType::AtMost(_) => at_most = true, + GroupType::Exact(_) => exact = true, + GroupType::AtLeast(_) => at_least = true, + GroupType::AtMost(_) => at_most = true, } if exact && at_least && at_most { @@ -417,8 +417,8 @@ Setter for the [`{}::{field_ident}`] field. ) } - fn impl_set_input_type(&self, field: &'info info::Field) -> Option { - if field.kind() == &info::FieldKind::Skipped { + fn impl_set_input_type(&self, field: &'info Field) -> Option { + if field.kind() == &FieldKind::Skipped { return None; } let field_ident = field.ident(); @@ -438,8 +438,8 @@ Setter for the [`{}::{field_ident}`] field. Some(quote!(#field_ident: #field_ty)) } - fn impl_set_input_value(&self, field: &'info info::Field) -> Option { - if field.kind() == &info::FieldKind::Skipped { + fn impl_set_input_value(&self, field: &'info Field) -> Option { + if field.kind() == &FieldKind::Skipped { return None; } @@ -457,7 +457,7 @@ Setter for the [`{}::{field_ident}`] field. quote!(#field_ident) }; - if field.kind() == &info::FieldKind::Optional { + if field.kind() == &FieldKind::Optional { Some(quote!(#field_value)) } else { Some(quote!(Some(#field_value))) diff --git a/const_typed_builder_derive/src/generator/data_generator.rs b/const_typed_builder_derive/src/generator/data_generator.rs index 0bf2f3a..5f8cf8f 100644 --- a/const_typed_builder_derive/src/generator/data_generator.rs +++ b/const_typed_builder_derive/src/generator/data_generator.rs @@ -1,11 +1,11 @@ -use crate::info; +use crate::info::{Container, FieldKind}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; /// The `DataGenerator` struct is responsible for generating code related to the data struct /// that corresponds to the target struct and the conversion implementations. pub struct DataGenerator<'a> { - info: &'a info::Container<'a>, + info: &'a Container<'a>, } impl<'a> DataGenerator<'a> { @@ -13,12 +13,12 @@ impl<'a> DataGenerator<'a> { /// /// # Arguments /// - /// - `info`: The `info::Container` containing all the information of the data container. + /// - `info`: The `Container` containing all the information of the data container. /// /// # Returns /// /// A `DataGenerator` instance initialized with the provided information. - pub fn new(info: &'a info::Container<'a>) -> Self { + pub fn new(info: &'a Container<'a>) -> Self { Self { info } } @@ -99,16 +99,14 @@ impl<'a> DataGenerator<'a> { let field_ident = field.ident(); let data_field_type = match field.kind() { - info::FieldKind::Skipped => return None, - info::FieldKind::Optional => field.ty().to_token_stream(), - info::FieldKind::Mandatory if field.is_option_type() => { - field.ty().to_token_stream() - } - info::FieldKind::Mandatory => { + 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 => { let ty = field.ty(); quote!(Option<#ty>) } - info::FieldKind::Grouped => field.ty().to_token_stream(), + FieldKind::Grouped => field.ty().to_token_stream(), }; let tokens = quote!( @@ -131,14 +129,14 @@ impl<'a> DataGenerator<'a> { .map(|field| { let field_ident = field.ident(); let tokens = match field.kind() { - info::FieldKind::Skipped => quote!(#field_ident: None), - info::FieldKind::Mandatory if field.is_option_type() => { + FieldKind::Skipped => quote!(#field_ident: None), + FieldKind::Mandatory if field.is_option_type() => { quote!(#field_ident: data.#field_ident) } - info::FieldKind::Optional | info::FieldKind::Grouped => { + FieldKind::Optional | FieldKind::Grouped => { quote!(#field_ident: data.#field_ident) } - info::FieldKind::Mandatory => { + FieldKind::Mandatory => { quote!(#field_ident: data.#field_ident.unwrap()) } }; @@ -157,7 +155,7 @@ impl<'a> DataGenerator<'a> { .info .field_collection() .iter() - .filter(|field| field.kind() != &info::FieldKind::Skipped) + .filter(|field| field.kind() != &FieldKind::Skipped) .map(|field| { let field_ident = field.ident(); quote!(#field_ident: None) diff --git a/const_typed_builder_derive/src/generator/target_generator.rs b/const_typed_builder_derive/src/generator/target_generator.rs index 39dae3f..bb43c2e 100644 --- a/const_typed_builder_derive/src/generator/target_generator.rs +++ b/const_typed_builder_derive/src/generator/target_generator.rs @@ -1,12 +1,12 @@ use super::util; -use crate::info; +use crate::info::Container; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; /// The `TargetGenerator` struct is responsible for generating code for the target struct implementation -/// of the builder pattern based on the provided `info::Container`. +/// of the builder pattern based on the provided `Container`. pub struct TargetGenerator<'info> { - info: &'info info::Container<'info>, + info: &'info Container<'info>, } impl<'info> TargetGenerator<'info> { @@ -14,12 +14,12 @@ impl<'info> TargetGenerator<'info> { /// /// # Arguments /// - /// - `info`: The `info::Container` containing all the information of the data container. + /// - `info`: The `Container` containing all the information of the data container. /// /// # Returns /// /// A `TargetGenerator` instance initialized with the provided information. - pub fn new(info: &'info info::Container<'info>) -> Self { + pub fn new(info: &'info Container<'info>) -> Self { Self { info } } From c6437cd59e32e72ac4517cf0751cc4de0c8bbf4c Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:46:13 +0200 Subject: [PATCH 11/15] Add parser suffix to file name --- .../src/parser/{container.rs => container_parser.rs} | 0 .../src/parser/{field.rs => field_parser.rs} | 0 .../src/parser/{group.rs => group_parser.rs} | 0 const_typed_builder_derive/src/parser/mod.rs | 12 ++++++------ 4 files changed, 6 insertions(+), 6 deletions(-) rename const_typed_builder_derive/src/parser/{container.rs => container_parser.rs} (100%) rename const_typed_builder_derive/src/parser/{field.rs => field_parser.rs} (100%) rename const_typed_builder_derive/src/parser/{group.rs => group_parser.rs} (100%) diff --git a/const_typed_builder_derive/src/parser/container.rs b/const_typed_builder_derive/src/parser/container_parser.rs similarity index 100% rename from const_typed_builder_derive/src/parser/container.rs rename to const_typed_builder_derive/src/parser/container_parser.rs diff --git a/const_typed_builder_derive/src/parser/field.rs b/const_typed_builder_derive/src/parser/field_parser.rs similarity index 100% rename from const_typed_builder_derive/src/parser/field.rs rename to const_typed_builder_derive/src/parser/field_parser.rs diff --git a/const_typed_builder_derive/src/parser/group.rs b/const_typed_builder_derive/src/parser/group_parser.rs similarity index 100% rename from const_typed_builder_derive/src/parser/group.rs rename to const_typed_builder_derive/src/parser/group_parser.rs diff --git a/const_typed_builder_derive/src/parser/mod.rs b/const_typed_builder_derive/src/parser/mod.rs index a3ef402..d9fe3d4 100644 --- a/const_typed_builder_derive/src/parser/mod.rs +++ b/const_typed_builder_derive/src/parser/mod.rs @@ -1,7 +1,7 @@ -mod container; -mod field; -mod group; +mod container_parser; +mod field_parser; +mod group_parser; -pub use container::ContainerParser; -use field::FieldParser; -use group::GroupParser; +pub use container_parser::ContainerParser; +use field_parser::FieldParser; +use group_parser::GroupParser; From 5b4545b8dc94a554349e71def878c5c65ae3c271 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 19:49:59 +0200 Subject: [PATCH 12/15] Fix `use` statements, remove `dbg!` --- .../src/generator/mod.rs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/const_typed_builder_derive/src/generator/mod.rs b/const_typed_builder_derive/src/generator/mod.rs index f00476a..d0f2a37 100644 --- a/const_typed_builder_derive/src/generator/mod.rs +++ b/const_typed_builder_derive/src/generator/mod.rs @@ -2,17 +2,16 @@ mod builder_generator; mod data_generator; mod target_generator; -use self::{ - builder_generator::BuilderGenerator, data_generator::DataGenerator, - target_generator::TargetGenerator, -}; -use crate::info; +use crate::info::Container; +use builder_generator::BuilderGenerator; +use data_generator::DataGenerator; use proc_macro2::TokenStream; use quote::quote; +use target_generator::TargetGenerator; /// The `Generator` struct is responsible for generating code for the builder pattern based on the provided `StructInfo`. pub struct Generator<'info> { - info: &'info info::Container<'info>, + info: &'info Container<'info>, data_gen: DataGenerator<'info>, target_gen: TargetGenerator<'info>, builder_gen: BuilderGenerator<'info>, @@ -23,12 +22,12 @@ impl<'info> Generator<'info> { /// /// # Arguments /// - /// - `info`: The `info::Container` containing all the information of the data container. + /// - `info`: The `Container` containing all the information of the data container. /// /// # Returns /// /// A `Generator` instance initialized with the provided `StructInfo`. - pub fn new(info: &'info info::Container<'info>) -> Self { + pub fn new(info: &'info Container<'info>) -> Self { info.group_collection() .values() .for_each(|group| group.check()); @@ -73,10 +72,10 @@ impl<'info> Generator<'info> { } mod util { + use crate::info::{FieldCollection, TrackedField}; use proc_macro2::TokenStream; use quote::quote; - use crate::info; /// Generates const generics with boolean values and returns a token stream. /// /// # Arguments @@ -88,12 +87,12 @@ mod util { /// A `TokenStream` representing the generated const generics. pub fn const_generics_all_valued( value: bool, - fields: &info::FieldCollection, + fields: &FieldCollection, generics: &syn::Generics, ) -> TokenStream { let mut all = fields .iter() - .filter_map(info::TrackedField::new) + .filter_map(TrackedField::new) .map(|_| quote!(#value)); add_const_valued_generics_for_type(&mut all, generics) } @@ -107,7 +106,6 @@ mod util { constants: &mut impl Iterator, generics: &syn::Generics, ) -> TokenStream { - dbg!(generics.type_params().collect::>()); let generic_idents = generics.params.iter().map(|param| match param { syn::GenericParam::Lifetime(lt) => <.lifetime.ident, syn::GenericParam::Type(ty) => &ty.ident, From 37a9c44c3ddfc950497ee1dbdcb39a4572f91557 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 20:08:48 +0200 Subject: [PATCH 13/15] Remove duplicate test See optional_mandatory_1.rs --- .../compile_fail/optional_mandatory_set_1.rs | 11 ----------- .../compile_fail/optional_mandatory_set_1.stderr | 15 --------------- 2 files changed, 26 deletions(-) delete mode 100644 const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs delete mode 100644 const_typed_builder_test/compile_fail/optional_mandatory_set_1.stderr diff --git a/const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs b/const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs deleted file mode 100644 index 5ae2524..0000000 --- a/const_typed_builder_test/compile_fail/optional_mandatory_set_1.rs +++ /dev/null @@ -1,11 +0,0 @@ -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_set_1.stderr b/const_typed_builder_test/compile_fail/optional_mandatory_set_1.stderr deleted file mode 100644 index e2f450e..0000000 --- a/const_typed_builder_test/compile_fail/optional_mandatory_set_1.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0308]: mismatched types - --> ./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` - | | - | arguments to this method are incorrect - | - = note: expected struct `String` - found enum `Option` -note: method defined here - --> ./compile_fail/optional_mandatory_set_1.rs:7:9 - | -7 | bar: Option, - | ^^^--------------- From f10372d2362d6cde4cbd419786993791f21c0f75 Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 20:12:09 +0200 Subject: [PATCH 14/15] Remove passing test from compile_fail It now generates a warning; the second attribute simply doesn't have an effect --- .../compile_fail/group_3.rs | 14 ------------- .../compile_fail/group_3.stderr | 21 ------------------- 2 files changed, 35 deletions(-) delete mode 100644 const_typed_builder_test/compile_fail/group_3.rs delete mode 100644 const_typed_builder_test/compile_fail/group_3.stderr diff --git a/const_typed_builder_test/compile_fail/group_3.rs b/const_typed_builder_test/compile_fail/group_3.rs deleted file mode 100644 index 2a90345..0000000 --- a/const_typed_builder_test/compile_fail/group_3.rs +++ /dev/null @@ -1,14 +0,0 @@ -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 deleted file mode 100644 index 0a11742..0000000 --- a/const_typed_builder_test/compile_fail/group_3.stderr +++ /dev/null @@ -1,21 +0,0 @@ -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` From 9a6d74e5bb64bd880469a67a5d74d702fb77a2bd Mon Sep 17 00:00:00 2001 From: koenichiwa <47349524+koenichiwa@users.noreply.github.com> Date: Mon, 9 Oct 2023 20:15:34 +0200 Subject: [PATCH 15/15] Remove either from dependencies --- const_typed_builder_derive/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/const_typed_builder_derive/Cargo.toml b/const_typed_builder_derive/Cargo.toml index 730eeab..235ab47 100644 --- a/const_typed_builder_derive/Cargo.toml +++ b/const_typed_builder_derive/Cargo.toml @@ -16,7 +16,6 @@ readme.workspace = true syn = { version = "2.0", features = ["full", "extra-traits"] } quote = "1.0" proc-macro2 = "1.0" -either = "1.9" itertools = "0.11.0" convert_case = "0.6" proc-macro-error = "1.0"