idanarye / rust-typed-builder

Compile-time type-checked builder derive
https://crates.io/crates/typed-builder
Apache License 2.0
904 stars 52 forks source link

Using mutators for generic fields #149

Closed Zizico2 closed 1 month ago

Zizico2 commented 2 months ago

Using mutators on a generic field doesn't compile: error[E0401]: can't use generic parameters from outer item.

idanarye commented 1 month ago

I've tried this, and it really showed that error:

use std::ops::Add;

use typed_builder::TypedBuilder;

#[derive(TypedBuilder)]
struct Foo<T: Add<T> + Default> {
    #[builder(
        via_mutators,
        mutators(
            fn add(self, amount: T) {
                self.bar += amount;
            }
        )
    )]
    bar: T,
}

Using cargo expand to expand the derive macro and then try to build on it, the problem is from here:

#[allow(dead_code, non_camel_case_types, missing_docs)]
#[automatically_derived]
impl<T: Add<T> + Default> FooBuilder<T, ((T,),)> {
    #[allow(clippy::used_underscore_binding, clippy::no_effect_underscore_binding)]
    fn add(self, amount: T) -> FooBuilder<T, ((T,),)> {
        struct TypedBuilderFieldMutator {
            bar: T,
        }
        impl TypedBuilderFieldMutator {
            fn add(&mut self, amount: T) {
                self.bar += amount;
            }
        }
        let __args = (amount);
        let ((bar,),) = self.fields;
        let mut __mutator = TypedBuilderFieldMutator { bar };
        {
            let (amount) = __args;
            __mutator.add(amount);
        }
        let TypedBuilderFieldMutator { bar } = __mutator;
        FooBuilder {
            fields: ((bar,),),
            phantom: self.phantom,
        }
    }
}

Specifically - TypedBuilderFieldMutator which uses T. Rust does not like that - inline types are only "inline" for visibility purposes (and ergonomics, of course) - they don't actually share the generic parametrization of whatever they are defined in.

I'll have to change it to emit this:

struct TypedBuilderFieldMutator<T: Add<T> + Default> {
    bar: T,
    _phantom: ::core::marker::PhantomData<T>,
}

Note that in this specific case we don't need the phantom data, but since the mutator does not necessarily include all the fields it may not use all the generic parameters, and I really don't want to start diagnosing the generics and see which ones are needed so I'm just going to add them all.