Skip to content

Commit

Permalink
Fix #149 - mutators for generic fields
Browse files Browse the repository at this point in the history
  • Loading branch information
idanarye committed Jul 14, 2024
1 parent 4b12db7 commit 9451802
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Fixed
- Fix mutators for generic fields (see issue #149)

## 0.19.0 - 2024-06-15
### Added
Expand Down
18 changes: 18 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -936,3 +936,21 @@ fn test_mutators_field() {
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 });
}

#[test]
fn test_mutators_for_generic_fields() {
use core::ops::AddAssign;

#[derive(Debug, PartialEq, TypedBuilder)]
struct Foo<S: Default + AddAssign, T: Default + AddAssign> {
#[builder(via_mutators, mutators(
fn x_plus(self, plus: S) {
self.x += plus;
}
))]
x: S,
y: T,
}

assert_eq!(Foo::builder().x_plus(1).y(2).build(), Foo { x: 1, y: 2 });
}
34 changes: 17 additions & 17 deletions typed-builder-macro/src/struct_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::builder_attr::{IntoSetting, TypeBuilderAttr};
use crate::field_info::FieldInfo;
use crate::mutator::Mutator;
use crate::util::{
empty_type, empty_type_tuple, first_visibility, modify_types_generics_hack, public_visibility, strip_raw_ident_prefix,
type_tuple,
empty_type, empty_type_tuple, first_visibility, modify_types_generics_hack, phantom_data_for_generics, public_visibility,
strip_raw_ident_prefix, type_tuple,
};

#[derive(Debug)]
Expand Down Expand Up @@ -103,17 +103,7 @@ impl<'a> StructInfo<'a> {
let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| {
args.push(syn::GenericArgument::Type(init_fields_type.clone().into()));
});
let phantom_generics = self.generics.params.iter().filter_map(|param| match param {
syn::GenericParam::Lifetime(lifetime) => {
let lifetime = &lifetime.lifetime;
Some(quote!(&#lifetime ()))
}
syn::GenericParam::Type(ty) => {
let ty = &ty.ident;
Some(ty.to_token_stream())
}
syn::GenericParam::Const(_cnst) => None,
});
let phantom_data = phantom_data_for_generics(self.generics);

let builder_method_name = self.builder_attr.builder_method.get_name().unwrap_or_else(|| quote!(builder));
let builder_method_visibility = first_visibility(&[
Expand Down Expand Up @@ -193,7 +183,7 @@ impl<'a> StructInfo<'a> {
#[allow(dead_code, non_camel_case_types, non_snake_case)]
#builder_type_visibility struct #builder_name #b_generics #b_generics_where_extras_predicates {
fields: #all_fields_param,
phantom: ::core::marker::PhantomData<(#( ::core::marker::PhantomData<#phantom_generics> ),*)>,
phantom: #phantom_data,
}

#[automatically_derived]
Expand Down Expand Up @@ -472,24 +462,33 @@ impl<'a> StructInfo<'a> {
let fn_name = &sig.ident;
let mutator_args = mutator.arguments();

// Generics for the mutator - should be similar to the struct's generics
let m_generics = &self.generics;
let (m_impl_generics, m_ty_generics, m_where_clause) = m_generics.split_for_impl();
let m_phantom = phantom_data_for_generics(self.generics);

Ok(quote! {
#[allow(dead_code, non_camel_case_types, missing_docs)]
#[automatically_derived]
impl #impl_generics #builder_name <#ty_generics> #where_clause {
#(#attrs)*
#[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)]
#vis #sig {
struct #mutator_struct_name {
struct #mutator_struct_name #m_generics #m_where_clause {
__phantom: #m_phantom,
#mutator_ty_fields
}
impl #mutator_struct_name {
impl #m_impl_generics #mutator_struct_name #m_ty_generics #m_where_clause {
#mutator_fn
}

let __args = (#mutator_args);

let ( #destructuring ) = self.fields;
let mut __mutator = #mutator_struct_name{ #mutator_destructure_fields };
let mut __mutator: #mutator_struct_name #m_ty_generics = #mutator_struct_name {
__phantom: ::core::default::Default::default(),
#mutator_destructure_fields
};

// This dance is required to keep mutator args and destrucutre fields from interfering.
{
Expand All @@ -498,6 +497,7 @@ impl<'a> StructInfo<'a> {
}

let #mutator_struct_name {
__phantom,
#mutator_destructure_fields
} = __mutator;

Expand Down
17 changes: 16 additions & 1 deletion typed-builder-macro/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::iter;

use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use quote::{format_ident, ToTokens};
use quote::{format_ident, quote, ToTokens};
use syn::{
parenthesized,
parse::{Parse, ParseStream, Parser},
Expand Down Expand Up @@ -357,3 +357,18 @@ pub fn pat_to_ident(i: usize, pat: &Pat) -> Ident {
format_ident!("__{i}", span = pat.span())
}
}

pub fn phantom_data_for_generics(generics: &syn::Generics) -> proc_macro2::TokenStream {
let phantom_generics = generics.params.iter().filter_map(|param| match param {
syn::GenericParam::Lifetime(lifetime) => {
let lifetime = &lifetime.lifetime;
Some(quote!(&#lifetime ()))
}
syn::GenericParam::Type(ty) => {
let ty = &ty.ident;
Some(ty.to_token_stream())
}
syn::GenericParam::Const(_cnst) => None,
});
quote!(::core::marker::PhantomData<(#( ::core::marker::PhantomData<#phantom_generics> ),*)>)
}

0 comments on commit 9451802

Please sign in to comment.