Skip to content

Conversation

treysidechain
Copy link

Closes #44

This would support the example in #44

#[derive(TypedBuilder)]
struct Foo<T> {
    #[builder(default = 12)]
    x: T,
}

if and only if a default generic type is specified for T like so

#[derive(TypedBuilder)]
struct Foo<T = usize> {
    #[builder(default = 12)]
    x: T,
}

#44 (comment)

Finally - this feels like breaking the Rust rule that the information about the types should be in the signatures, not in the implementation body. And yes, you can argue that the attributes are part of the signature, but #[builder(default)] acts as an implementing code generator and it feels wrong to have it affect the signature...

I totally agree with your assessment here. Specifying the default type for generic parameters in order to enable this feature feels like a happy medium since it keeps all the default type information in the struct signature.

I also added some tests and examples which show how this new feature interacts with more complex types that use both generic parameters with and without defaults. The code might not be the cleanest at the moment, just wanted to get a working model up first and then can refactor as needed. Let me know what you think!

let builder_name = strip_raw_ident_prefix(format!("{}Builder", ast.ident));

let mut generics_without_defaults = ast.generics.clone();
generics_without_defaults.params = generics_without_defaults
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be easier change them in-place? Something like (haven't tested it):

for param in generics_without_defaults.params.iter_mut() {
    match param {
        syn::GenericParam::Type(type_param) => {
            type_param.eq_token = None;
            type_param.default = None;
        }
        syn::GenericParam::Const(const_param) => {
            const_param.eq_token = None;
            const_param.default = None;
        }
        _ => {}
    }
}

.into_iter()
.map::<Result<syn::GenericArgument, _>, _>(|param| match param {
syn::GenericParam::Type(type_param) => match type_param.default {
Some(default) => syn::parse(proc_macro::TokenStream::from(quote!(#default))),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})
.collect();

let ty_generics_with_defaults: Punctuated<_, syn::token::Comma> = ast
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding the code correctly, does this mean that you are generating a list of all the generics for passing to things that accept generic parameters, where the generics with default type/value will have that type and the ones without will just have their identifiers? So e.g. <A, B = usize, const C: usize, const D: usize = 7> (yes, I know it's without the <>, I added them for aesthetics) will become <A, usize, C, 7>?

If so, the name is a bit misleading - it implies either that we are getting the generics together with their default values, or that we are getting only the generics that have a default values (the other ones are removed)

let ty = &field.ty;
let mut ty_str = format!("{}", quote! { #ty });
for (generic_param, regular_expression, default_type) in generic_defaults.iter() {
if regular_expression.is_match(&ty_str) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you doing this to determine if the type depends on the generic parameter? I'd prefer doing this with https://docs.rs/syn/latest/syn/visit/index.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

#[builder(default = ...)] for generic field got error "expected type parameter, found concrete type"

2 participants