Repository: BrynCooke/buildstructor (language: rust)

Issue:
Fails to generate valid code for map with generics
```rust
 impl Foo {
    fn new<K: Into<String>, V: Into<String>>(param: HashMap<K, V>) -> Foo {
        Self {
            param: param
                .into_iter()
                .map(|(k, v)| (k.into(), v.into()))
                .collect(),
        }
    }
}
```


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f04d2c..09317a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## 0.1.3 - 2022-03-30
+### Fixed
+[#1](https://github.com/BrynCooke/buildstructor/issues/1) Fix generics on collections
 
 ## 0.1.2 - 2022-03-30
 ### Changed
diff --git a/Cargo.toml b/Cargo.toml
index e4f7595..fe03b4a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ keywords = ["derive", "macro", "builder", "constructor"]
 
 [lib]
 proc-macro = true
+doctest = false
 
 [dependencies]
 lazy_static = "1.4.0"
diff --git a/README.md b/README.md
index b746da5..638c4f3 100644
--- a/README.md
+++ b/README.md
@@ -124,7 +124,7 @@ To create a fallible builder just make your constructor fallible using `Result`.
 ```rust
 #[builder]
 impl MyStruct {
-    fn new(param: Option<usize>) -> Result<MyStruct, Box<dyn Error>> {
+    fn new(param: usize) -> Result<MyStruct, Box<dyn Error>> {
         Ok(Self { param })
     }
 }
diff --git a/examples/collections_generics.rs b/examples/collections_generics.rs
new file mode 100644
index 0000000..6735a44
--- /dev/null
+++ b/examples/collections_generics.rs
@@ -0,0 +1,45 @@
+use std::collections::{HashMap, HashSet};
+
+use buildstructor::builder;
+
+pub struct Collections {
+    map: HashMap<String, String>,
+    set: HashSet<String>,
+}
+
+#[builder]
+impl Collections {
+    fn new<K: Into<String>, V: Into<String>>(map: HashMap<K, V>, set: HashSet<K>) -> Collections {
+        Self {
+            map: map.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
+            set: set.into_iter().map(|v| v.into()).collect(),
+        }
+    }
+}
+
+fn main() {
+    let collections = Collections::builder()
+        .map_entry("Appa", "1")
+        .map_entry("Momo", "2")
+        .set_entry("Aang")
+        .set_entry("Katara")
+        .set_entry("Sokka")
+        .set_entry("Toph")
+        .build();
+    assert_eq!(
+        collections.map,
+        HashMap::from([
+            ("Appa".to_string(), "1".to_string()),
+            ("Momo".to_string(), "2".to_string())
+        ])
+    );
+    assert_eq!(
+        collections.set,
+        HashSet::from([
+            ("Aang".to_string()),
+            ("Katara".to_string()),
+            ("Sokka".to_string()),
+            ("Toph".to_string()),
+        ])
+    );
+}
diff --git a/src/buildstructor/analyze.rs b/src/buildstructor/analyze.rs
index e313483..eb19af5 100644
--- a/src/buildstructor/analyze.rs
+++ b/src/buildstructor/analyze.rs
@@ -107,4 +107,9 @@ mod tests {
     fn collection_test() {
         analyze(collections_test_case()).unwrap();
     }
+
+    #[test]
+    fn collection_generics_test() {
+        analyze(collections_generics_test_case()).unwrap();
+    }
 }
diff --git a/src/buildstructor/codegen.rs b/src/buildstructor/codegen.rs
index 62dad7c..e5f2759 100644
--- a/src/buildstructor/codegen.rs
+++ b/src/buildstructor/codegen.rs
@@ -34,8 +34,8 @@ pub fn codegen(ir: Ir) -> Result<TokenStream> {
     let (_, all_ty_generics, _) = all_generics.split_for_impl();
 
     let method_generics = &ir.method_generics;
-    let builder_generics = Generics::combine(vec![&ir.method_generics, &ir.generics]);
-    let target_generics_raw: Vec<GenericArgument> = builder_generics
+    let builder_init_generics = Generics::combine(vec![&ir.method_generics, &ir.generics]);
+    let target_generics_raw: Vec<GenericArgument> = builder_init_generics
         .to_generic_args()
         .args
         .into_iter()
@@ -54,7 +54,7 @@ pub fn codegen(ir: Ir) -> Result<TokenStream> {
 
     Ok(quote! {
         impl #impl_generics #target_name #ty_generics #where_clause {
-            #vis fn builder #method_generics() -> #module_name::#builder_name<#builder_state_type_initial, #(#target_generics_raw) *> {
+            #vis fn builder #method_generics() -> #module_name::#builder_name<#builder_state_type_initial, #(#target_generics_raw), *> {
                 #module_name::new()
             }
         }
@@ -63,7 +63,7 @@ pub fn codegen(ir: Ir) -> Result<TokenStream> {
         mod #module_name {
             use super::*;
 
-            pub fn new #builder_generics() -> #builder_name<#builder_state_type_initial, #(#target_generics_raw), *>
+            pub fn new #builder_init_generics() -> #builder_name<#builder_state_type_initial, #(#target_generics_raw), *>
             {
                 #builder_name {
                     fields: (#(#builder_state_initial ,) *),
@@ -163,7 +163,10 @@ pub fn builder_methods(ir: &Ir) -> Result<Vec<TokenStream>> {
             let new_state = params(ir, idx, field_name, &builder_type_generics, set);
             let set_some = call(format_ident!("__set"), vec![call(format_ident!("Some"), vec![Expr::Path(field_name.to_expr_path())])]);
             let new_state_option = params(ir, idx, field_name, &builder_type_generics, set_some);
-            let builder_type_generics = Generics::combine(vec![&builder_type_generics.without(idx), &builder_generics]);
+
+
+            let mut builder_type_generics = Generics::combine(vec![&builder_type_generics.without(idx), &builder_generics]);
+
             let generic_param = ty.generic_args();
 
             match f.field_type {
@@ -188,8 +191,11 @@ pub fn builder_methods(ir: &Ir) -> Result<Vec<TokenStream>> {
                 },
                 FieldType::Set => {
                     let (singular, plural) = single_plural_names(field_name);
-                    let field_collection_type = &f.collection_type;
+                    let field_collection_type = &f.collection_type.as_ref().unwrap().raw_ident().unwrap();
                     let index = Index::from(idx);
+                    builder_type_generics = builder_type_generics.add_bound(field_collection_type, &Type::parse("core::cmp::Eq"))
+                        .add_bound(field_collection_type, &Type::parse("core::hash::Hash"));
+
                     quote! {
                         impl #builder_type_generics #builder_name #before {
 
@@ -228,9 +234,11 @@ pub fn builder_methods(ir: &Ir) -> Result<Vec<TokenStream>> {
                 },
                 FieldType::Map => {
                     let (singular, plural) = single_plural_names(field_name);
-                    let field_key_type = &f.key_type;
+                    let field_key_type = &f.key_type.as_ref().unwrap().raw_ident().unwrap();
                     let field_value_type = &f.value_type;
                     let index = Index::from(idx);
+                    builder_type_generics = builder_type_generics.add_bound(field_key_type, &Type::parse("core::cmp::Eq"))
+                        .add_bound(field_key_type, &Type::parse("core::hash::Hash"));
                     quote! {
                         impl #builder_type_generics #builder_name #before {
 
@@ -373,4 +381,9 @@ mod tests {
     fn collection_test() {
         assert_codegen!(collections_test_case());
     }
+
+    #[test]
+    fn collection_generics_test() {
+        assert_codegen!(collections_generics_test_case());
+    }
 }
diff --git a/src/buildstructor/mod.rs b/src/buildstructor/mod.rs
index 5f31705..3067674 100644
--- a/src/buildstructor/mod.rs
+++ b/src/buildstructor/mod.rs
@@ -139,4 +139,20 @@ mod tests {
             }
         )
     }
+
+    pub fn collections_generics_test_case() -> Ast {
+        parse_quote!(
+            #[builder]
+            impl Foo {
+                fn new<K: Into<String>, V: Into<String>>(param: HashMap<K, V>) -> Foo {
+                    Self {
+                        param: param
+                            .into_iter()
+                            .map(|(k, v)| (k.into(), v.into()))
+                            .collect(),
+                    }
+                }
+            }
+        )
+    }
 }
diff --git a/src/buildstructor/utils.rs b/src/buildstructor/utils.rs
index 5698095..c8e44f2 100644
--- a/src/buildstructor/utils.rs
+++ b/src/buildstructor/utils.rs
@@ -1,3 +1,4 @@
+use quote::format_ident;
 use syn::punctuated::Punctuated;
 use syn::{
     AngleBracketedGenericArguments, Expr, ExprPath, ExprTuple, GenericArgument, GenericParam,
@@ -57,6 +58,7 @@ pub trait GenericsExt {
     fn to_expr_tuple(&self, populate: impl Fn(usize, &TypeParam) -> Expr) -> ExprTuple;
     fn without(self, idx: usize) -> Self;
     fn combine(generics: Vec<&Generics>) -> Generics;
+    fn add_bound(self, ident: &Ident, ty: &Type) -> Self;
 }
 
 impl GenericsExt for Generics {
@@ -128,6 +130,24 @@ impl GenericsExt for Generics {
             ..Default::default()
         }
     }
+
+    fn add_bound(mut self, ident: &Ident, ty: &Type) -> Self {
+        if let Some(ty) = ty.to_path() {
+            self.params.iter_mut().for_each(|p| {
+                if let GenericParam::Type(t) = p {
+                    if t.ident == *ident {
+                        t.bounds.push(TypeParamBound::Trait(TraitBound {
+                            paren_token: None,
+                            modifier: TraitBoundModifier::None,
+                            lifetimes: None,
+                            path: ty.clone(),
+                        }));
+                    }
+                }
+            });
+        }
+        self
+    }
 }
 
 pub trait AngleBracketedGenericArgumentsExt {
@@ -168,6 +188,7 @@ pub trait TypeExt {
     fn generic_args(&self) -> Option<&Punctuated<GenericArgument, Token![,]>>;
     fn wrap_in_generic(&self, ident: Ident) -> Type;
     fn to_path(&self) -> Option<Path>;
+    fn parse(name: &'static str) -> Type;
 }
 
 impl TypeExt for Type {
@@ -218,6 +239,19 @@ impl TypeExt for Type {
         }
         None
     }
+
+    fn parse(name: &'static str) -> Type {
+        Type::Path(TypePath {
+            qself: None,
+            path: Path {
+                leading_colon: Default::default(),
+                segments: Punctuated::from_iter(
+                    name.split("::")
+                        .map(|s| PathSegment::from(format_ident!("{}", s))),
+                ),
+            },
+        })
+    }
 }
 
 pub trait PunctuatedExt<T, P> {
diff --git a/src/lib.rs b/src/lib.rs
index ffae4db..5b71962 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -33,10 +33,9 @@ use crate::buildstructor::parse;
 ///     }
 /// }
 ///
-/// fn main() {
-///     let mine = MyStruct::builder().a(2).b(3).build();
-///     assert_eq!(mine.sum, 5);
-/// }
+/// let mine = MyStruct::builder().a(2).b(3).build();
+/// assert_eq!(mine.sum, 5);
+///
 /// ```
 #[proc_macro_attribute]
 pub fn builder(attr: TokenStream, item: TokenStream) -> TokenStream {


Test patch:
diff --git a/src/buildstructor/snapshots/buildstructor__buildstructor__codegen__tests__collection_generics_test.snap b/src/buildstructor/snapshots/buildstructor__buildstructor__codegen__tests__collection_generics_test.snap
new file mode 100644
index 0000000..6a8f575
--- /dev/null
+++ b/src/buildstructor/snapshots/buildstructor__buildstructor__codegen__tests__collection_generics_test.snap
@@ -0,0 +1,96 @@
+---
+source: src/buildstructor/codegen.rs
+expression: output
+---
+impl Foo {
+    fn builder<K: Into<String>, V: Into<String>>() -> __foo_builder::__FooBuilder<
+            (__Optional<HashMap<K, V>>,),
+            K,
+            V,
+        > {
+        __foo_builder::new()
+    }
+}
+use __foo_builder::*;
+mod __foo_builder {
+    use super::*;
+    pub fn new<K: Into<String>, V: Into<String>>() -> __FooBuilder<
+            (__Optional<HashMap<K, V>>,),
+            K,
+            V,
+        > {
+        __FooBuilder {
+            fields: (__optional(),),
+            phantom: core::default::Default::default(),
+        }
+    }
+    pub struct __Required<T> {
+        phantom: std::marker::PhantomData<T>,
+    }
+    pub struct __Optional<T> {
+        lazy: Option<T>,
+    }
+    pub struct __Set<T> {
+        value: T,
+    }
+    fn __set<T>(value: T) -> __Set<T> {
+        __Set { value }
+    }
+    fn __required<T>() -> __Required<T> {
+        __Required::<T> {
+            phantom: core::default::Default::default(),
+        }
+    }
+    fn __optional<T>() -> __Optional<T> {
+        __Optional::<T> { lazy: None }
+    }
+    impl<T: Default> From<__Optional<T>> for __Set<T> {
+        fn from(o: __Optional<T>) -> Self {
+            __Set {
+                value: o.lazy.unwrap_or_default(),
+            }
+        }
+    }
+    pub struct __FooBuilder<__P, K, V> {
+        fields: __P,
+        phantom: (core::marker::PhantomData<K>, core::marker::PhantomData<V>),
+    }
+    impl<
+        K: Into<String> + core::cmp::Eq + core::hash::Hash,
+        V: Into<String>,
+    > __FooBuilder<(__Optional<HashMap<K, V>>,), K, V> {
+        pub fn param(
+            mut self,
+            param: HashMap<K, V>,
+        ) -> __FooBuilder<(__Optional<HashMap<K, V>>,), K, V> {
+            self.fields
+                .0
+                .lazy
+                .get_or_insert_with(|| core::default::Default::default())
+                .extend(param.into_iter());
+            self
+        }
+        pub fn param_entry(
+            mut self,
+            key: K,
+            value: V,
+        ) -> __FooBuilder<(__Optional<HashMap<K, V>>,), K, V> {
+            self.fields
+                .0
+                .lazy
+                .get_or_insert_with(|| core::default::Default::default())
+                .insert(key, value);
+            self
+        }
+    }
+    impl<
+        K: Into<String>,
+        V: Into<String>,
+        __P0: Into<__Set<HashMap<K, V>>>,
+    > __FooBuilder<(__P0,), K, V> {
+        pub fn build(self) -> Foo {
+            Foo::new(self.fields.0.into().value)
+        }
+    }
+}
+