Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify default field value for field with type that uses generic parameters with defaults #73

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

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!

@@ -29,18 +40,137 @@ impl<'a> StructInfo<'a> {
pub fn new(ast: &'a syn::DeriveInput, fields: impl Iterator<Item = &'a syn::Field>) -> Result<StructInfo<'a>, Error> {
let builder_attr = TypeBuilderAttr::new(&ast.attrs)?;
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