Skip to content

Commit

Permalink
Add mutators on fields
Browse files Browse the repository at this point in the history
  • Loading branch information
ModProg committed Sep 26, 2023
1 parent 3d284ec commit ff068e8
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 33 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]
### Added
- `#[builder(mutators(...))]` to generate functions on `Builder` able to
mutate fields
- `#[builder(mutators(...))]` to generate functions on builder to mutate fields
- `#[builder(via_mutator)]` on fields to allow defining fields initialized
during `::builder()` for use with `mutators`

Expand Down
19 changes: 17 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ use core::ops::FnOnce;
/// #[derive(Default)]
/// struct Point { x: f32, y: f32 }
/// ```
///
/// - `mutators(...)` takes functions, that can mutate fields inside of the builder.
/// See [mutators](#mutators) for details.
///
Expand All @@ -143,6 +144,9 @@ use core::ops::FnOnce;
/// - `via_mutators = …` or `via_mutators(init = …)`: initialies the field with the expression `…`
/// when constructing the builder, useful in combination with [mutators](#mutators).
///
/// - `mutators(...)` takes functions, that can mutate fields inside of the builder.
/// Mutators specified on a field, mark this field as required, see [mutators](#mutators) for details.
///
/// - `setter(...)`: settings for the field setters. The following values are permitted inside:
///
/// - `doc = "…"`: sets the documentation for the field's setter on the builder type. This will be
Expand Down Expand Up @@ -184,6 +188,10 @@ use core::ops::FnOnce;
/// fields can be specified via `self: (field1, field2)` in the mutator's function signature.
/// Mutators that require non `via_mutators` fields, will only be availible if these fields are set.
///
/// Mutators on a field, result in them automatically making the field required, i.e., its setter
/// needs to be executed or it needs to be marked as `via_mutators`. Appart from that, they behave
/// identically.
///
/// ```
/// use typed_builder::TypedBuilder;
///
Expand All @@ -200,6 +208,13 @@ use core::ops::FnOnce;
/// }
/// ))]
/// struct Struct {
/// // Does not require `self: (x)`, due to always
// // requiring, the field the mutator is specifed on.
/// #[builder(mutators(
/// fn x_into_b_field(self) {
/// self.b.push(self.x)
/// }
/// ))]
/// x: i32,
/// #[builder(via_mutators(init = 1))]
/// a: i32,
Expand All @@ -209,8 +224,8 @@ use core::ops::FnOnce;
///
/// // Mutators do not enforce only being called once
/// assert_eq!(
/// Struct::builder().x(2).x_into_b().x_into_b().inc_a(2).build(),
/// Struct {x: 2, a: 3, b: vec![2, 2]});
/// Struct::builder().x(2).x_into_b().x_into_b().x_into_b_field().inc_a(2).build(),
/// Struct {x: 2, a: 3, b: vec![2, 2, 2]});
/// ```
pub use typed_builder_macro::TypedBuilder;

Expand Down
33 changes: 33 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,3 +853,36 @@ fn test_mutators_item() {
let foo = Foo::builder().x(1).inc_x_by(4).inc_y_by_x().build();
assert_eq!(foo, Foo { x: 5, y: 5, z: 2, w: 2 });
}

#[test]
fn test_mutators_field() {
#[derive(Debug, PartialEq, TypedBuilder)]
#[builder(mutators())]
struct Foo {
#[builder(mutators(
fn inc_x(self: (x)) {
self.x += 1;
}
fn inc_y_by_x(self: (y)) {
self.y += self.x;
}
))]
x: i32,
#[builder(default)]
y: i32,
#[builder(via_mutators = 2, mutators(
fn inc_preset(self) {
self.z += 1;
self.w += 1;
}
))]
z: i32,
#[builder(via_mutators(init = 2))]
w: i32,
}

let foo = Foo::builder().x(1).inc_x().inc_preset().build();
assert_eq!(foo, Foo { x: 2, y: 0, z: 3, w: 3 });
let foo = Foo::builder().x(1).y(1).inc_y_by_x().build();
assert_eq!(foo, Foo { x: 1, y: 2, z: 2, w: 2 });
}
17 changes: 12 additions & 5 deletions typed-builder-macro/src/field_info.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote_spanned;
use syn::{parse::Error, spanned::Spanned, ItemFn};
use syn::{parse::Error, spanned::Spanned};

use crate::util::{
expr_to_lit_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, ApplyMeta, AttrArg, KeyValue,
expr_to_lit_string, ident_to_type, path_to_single_string, strip_raw_ident_prefix, ApplyMeta, AttrArg, KeyValue, Mutator,
};

#[derive(Debug)]
Expand All @@ -23,7 +23,7 @@ impl<'a> FieldInfo<'a> {
name,
generic_ident: syn::Ident::new(&format!("__{}", strip_raw_ident_prefix(name.to_string())), Span::call_site()),
ty: &field.ty,
builder_attr: field_defaults.with(&field.attrs)?,
builder_attr: field_defaults.with(&field.attrs, name)?,
}
.post_process()
} else {
Expand Down Expand Up @@ -117,7 +117,8 @@ pub struct FieldBuilderAttr<'a> {
pub via_mutators: Option<syn::Expr>,
pub deprecated: Option<&'a syn::Attribute>,
pub setter: SetterSettings,
pub mutators: Vec<ItemFn>,
/// Functions that are able to mutate fields in the builder that are already set
pub mutators: Vec<Mutator>,
}

#[derive(Debug, Default, Clone)]
Expand All @@ -133,7 +134,7 @@ pub struct SetterSettings {
}

impl<'a> FieldBuilderAttr<'a> {
pub fn with(mut self, attrs: &'a [syn::Attribute]) -> Result<Self, Error> {
pub fn with(mut self, attrs: &'a [syn::Attribute], name: &Ident) -> Result<Self, Error> {
for attr in attrs {
let list = match &attr.meta {
syn::Meta::List(list) => {
Expand Down Expand Up @@ -164,6 +165,10 @@ impl<'a> FieldBuilderAttr<'a> {
self.apply_subsections(list)?;
}

for mutator in self.mutators.iter_mut() {
mutator.required_fields.insert(name.clone());
}

self.inter_fields_conflicts()?;

Ok(self)
Expand Down Expand Up @@ -270,6 +275,8 @@ impl ApplyMeta for FieldBuilderAttr<'_> {
}
Ok(())
}

"mutators" => expr.sub_attr()?.undelimited().map(|fns| self.mutators.extend(fns)),
_ => Err(Error::new_spanned(
expr.name(),
format!("Unknown parameter {:?}", expr.name().to_string()),
Expand Down
5 changes: 3 additions & 2 deletions typed-builder-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
.filter(|f| f.builder_attr.default.is_none())
.map(|f| struct_info.required_field_impl(f));
let mutators = struct_info
.builder_attr
.mutators
.fields
.iter()
.flat_map(|f| &f.builder_attr.mutators)
.chain(&struct_info.builder_attr.mutators)
.map(|m| struct_info.mutator_impl(m))
.collect::<Result<TokenStream, _>>()?;
let build_method = struct_info.build_method_impl();
Expand Down
9 changes: 3 additions & 6 deletions typed-builder-macro/src/struct_info.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use std::collections::HashSet;

use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use quote::{format_ident, quote, ToTokens};
use syn::parse::Error;
use syn::punctuated::Punctuated;
use syn::{parse_quote, GenericArgument, ItemFn, Token};
Expand Down Expand Up @@ -417,12 +415,11 @@ impl<'a> StructInfo<'a> {
mutator @ Mutator {
fun: mutator_fn,
required_fields,
required_fields_span,
}: &Mutator,
) -> syn::Result<TokenStream> {
let StructInfo { ref builder_name, .. } = *self;

let mut required_fields: HashSet<_> = required_fields.iter().collect();
let mut required_fields = required_fields.clone();

let mut ty_generics = self.generic_argumnents();
let mut destructuring = TokenStream::new();
Expand All @@ -446,7 +443,7 @@ impl<'a> StructInfo<'a> {
ty_generics.push(syn::GenericArgument::Type(ty_generics_tuple.into()));
let (impl_generics, _, where_clause) = generics.split_for_impl();

let mutator_struct_name = Ident::new("TypedBuilderFieldMutator", *required_fields_span);
let mutator_struct_name = format_ident!("TypedBuilderFieldMutator");

let ItemFn { attrs, vis, .. } = mutator_fn;
let sig = mutator.outer_sig(parse_quote!(#builder_name <#ty_generics>));
Expand Down
23 changes: 7 additions & 16 deletions typed-builder-macro/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::iter;
use std::{collections::HashSet, iter};

use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, ToTokens};
Expand Down Expand Up @@ -308,21 +308,18 @@ pub trait ApplyMeta {
}
}


#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Mutator {
pub fun: ItemFn,
pub required_fields: Vec<Ident>,
pub required_fields_span: Span,
pub required_fields: HashSet<Ident>,
}

impl Parse for Mutator {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut fun: ItemFn = input.parse()?;

// Parse receiver
let requires;
let required_fields_span;
let required_fields;
if let Some(FnArg::Receiver(receiver)) = fun.sig.inputs.first_mut() {
// If `: (...)` was specified, this will define the required fields
if receiver.colon_token.is_some() {
Expand All @@ -338,17 +335,15 @@ impl Parse for Mutator {
))
}
};
requires = fields
required_fields = fields
.into_iter()
.map(|field| match field {
Type::Path(field) => field.path.require_ident().cloned(),
other => Err(syn::Error::new_spanned(other, "expected field identifier")),
})
.collect::<Result<_, _>>()?;
required_fields_span = receiver.ty.span();
} else {
requires = vec![];
required_fields_span = receiver.self_token.span();
required_fields = Default::default();
}
*receiver = parse_quote!(&mut self);
} else {
Expand All @@ -363,11 +358,7 @@ impl Parse for Mutator {
));
};

Ok(Self {
fun,
required_fields: requires,
required_fields_span,
})
Ok(Self { fun, required_fields })
}
}

Expand Down

0 comments on commit ff068e8

Please sign in to comment.