Repository: near/borsh-rs (rust)

Issue:
HashSet BorshSchema implementation
It seems that `impl BorshSchema for HashSet` is missing

Pull request:
impl BorshSchema for HashSet
closes #82 

Patch:
diff --git a/borsh-schema-derive-internal/Cargo.toml b/borsh-schema-derive-internal/Cargo.toml
index cb69d206..9e3ce7c2 100644
--- a/borsh-schema-derive-internal/Cargo.toml
+++ b/borsh-schema-derive-internal/Cargo.toml
@@ -15,3 +15,6 @@ Schema Generator for Borsh
 proc-macro2 = "1"
 syn = {version = "1", features = ["full", "fold"] }
 quote = "1"
+
+[dev-dependencies]
+pretty_assertions = "1.1.0"
diff --git a/borsh-schema-derive-internal/src/enum_schema.rs b/borsh-schema-derive-internal/src/enum_schema.rs
index bbc66d30..7b465479 100644
--- a/borsh-schema-derive-internal/src/enum_schema.rs
+++ b/borsh-schema-derive-internal/src/enum_schema.rs
@@ -5,15 +5,16 @@ use syn::{
     Visibility,
 };
 
-use crate::helpers::declaration;
+use crate::helpers::{declaration, quote_where_clause};
 
 pub fn process_enum(input: &ItemEnum, cratename: Ident) -> syn::Result<TokenStream2> {
     let name = &input.ident;
     let name_str = name.to_token_stream().to_string();
     let generics = &input.generics;
-    let (impl_generics, ty_generics, _) = generics.split_for_impl();
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
     // Generate function that returns the name of the type.
-    let (declaration, where_clause) = declaration(&name_str, &input.generics, cratename.clone());
+    let (declaration, where_clause_additions) =
+        declaration(&name_str, &input.generics, cratename.clone());
 
     // Generate function that returns the schema for variants.
     // Definitions of the variants.
@@ -113,11 +114,7 @@ pub fn process_enum(input: &ItemEnum, cratename: Ident) -> syn::Result<TokenStre
             Self::add_definition(Self::declaration(), definition, definitions);
         }
     };
-    let where_clause = if !where_clause.is_empty() {
-        quote! { where #(#where_clause),*}
-    } else {
-        TokenStream2::new()
-    };
+    let where_clause = quote_where_clause(where_clause, where_clause_additions);
     Ok(quote! {
         impl #impl_generics #cratename::BorshSchema for #name #ty_generics #where_clause {
             fn declaration() -> #cratename::schema::Declaration {
@@ -135,7 +132,7 @@ mod tests {
     use super::*;
 
     fn assert_eq(expected: TokenStream2, actual: TokenStream2) {
-        assert_eq!(expected.to_string(), actual.to_string())
+        pretty_assertions::assert_eq!(expected.to_string(), actual.to_string())
     }
 
     #[test]
@@ -324,4 +321,75 @@ mod tests {
         };
         assert_eq(expected, actual);
     }
+
+    #[test]
+    fn trailing_comma_generics() {
+        let item_struct: ItemEnum = syn::parse2(quote!{
+            enum Side<A, B>
+            where
+                A: Display + Debug,
+                B: Display + Debug,
+            {
+                Left(A),
+                Right(B),
+            }
+        })
+        .unwrap();
+
+        let actual = process_enum(
+            &item_struct,
+            Ident::new("borsh", proc_macro2::Span::call_site()),
+        )
+        .unwrap();
+        let expected = quote!{
+            impl<A, B> borsh::BorshSchema for Side<A, B>
+            where
+                A: Display + Debug,
+                B: Display + Debug,
+                A: borsh::BorshSchema,
+                B: borsh::BorshSchema
+            {
+                fn declaration() -> borsh::schema::Declaration {
+                    let params = borsh::maybestd::vec![<A>::declaration(), <B>::declaration()];
+                    format!(r#"{}<{}>"#, "Side", params.join(", "))
+                }
+                fn add_definitions_recursively(
+                    definitions: &mut borsh::maybestd::collections::HashMap<
+                        borsh::schema::Declaration,
+                        borsh::schema::Definition
+                    >
+                ) {
+                    #[derive(borsh :: BorshSchema)]
+                    struct SideLeft<A, B>
+                    (
+                        A, 
+                        #[borsh_skip] ::core::marker::PhantomData<(A, B, )>
+                    )
+                    where 
+                        A: Display + Debug, 
+                        B: Display + Debug,
+                    ;
+                    #[derive(borsh :: BorshSchema)]
+                    struct SideRight<A, B>
+                    (
+                        B, 
+                        #[borsh_skip] ::core::marker::PhantomData<(A, B, )>
+                    )
+                    where 
+                        A: Display + Debug, 
+                        B: Display + Debug,
+                    ;
+                    <SideLeft<A, B> >::add_definitions_recursively(definitions);
+                    <SideRight<A, B> >::add_definitions_recursively(definitions);
+                    let variants = borsh::maybestd::vec![
+                        ("Left".to_string(), <SideLeft<A, B> >::declaration()),
+                        ("Right".to_string(), <SideRight<A, B> >::declaration())
+                    ];
+                    let definition = borsh::schema::Definition::Enum { variants };
+                    Self::add_definition(Self::declaration(), definition, definitions);
+                }
+            }
+        };
+        assert_eq(expected, actual);
+    }
 }
diff --git a/borsh-schema-derive-internal/src/helpers.rs b/borsh-schema-derive-internal/src/helpers.rs
index ff7c1480..df7025e4 100644
--- a/borsh-schema-derive-internal/src/helpers.rs
+++ b/borsh-schema-derive-internal/src/helpers.rs
@@ -1,6 +1,6 @@
 use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, ToTokens};
-use syn::{Attribute, Generics, Ident, Meta};
+use syn::{Attribute, Generics, Ident, Meta, WhereClause};
 
 pub fn contains_skip(attrs: &[Attribute]) -> bool {
     for attr in attrs.iter() {
@@ -18,14 +18,9 @@ pub fn declaration(
     generics: &Generics,
     cratename: Ident,
 ) -> (TokenStream2, Vec<TokenStream2>) {
-    let (_, _, where_clause_generics) = generics.split_for_impl();
     // Generate function that returns the name of the type.
     let mut declaration_params = vec![];
     let mut where_clause = vec![];
-    if let Some(where_clause_generics) = where_clause_generics {
-        let where_clause_generics = &where_clause_generics.predicates;
-        where_clause.push(quote! {#where_clause_generics});
-    }
     for type_param in generics.type_params() {
         let type_param_name = &type_param.ident;
         declaration_params.push(quote! {
@@ -47,3 +42,21 @@ pub fn declaration(
     };
     (result, where_clause)
 }
+
+pub fn quote_where_clause(
+    where_clause: Option<&WhereClause>,
+    additions: Vec<TokenStream2>,
+) -> TokenStream2 {
+    if let Some(WhereClause { predicates, .. }) = where_clause {
+        // The original where clause might already have a trailing punctuation
+        if predicates.trailing_punct() {
+            quote! { where #predicates #(#additions),*}
+        } else {
+            quote! { where #predicates, #(#additions),*}
+        }
+    } else if additions.is_empty() {
+        TokenStream2::new()
+    } else {
+        quote! { where #(#additions),*}
+    }
+}
diff --git a/borsh-schema-derive-internal/src/struct_schema.rs b/borsh-schema-derive-internal/src/struct_schema.rs
index fe62ad00..93f3a11f 100644
--- a/borsh-schema-derive-internal/src/struct_schema.rs
+++ b/borsh-schema-derive-internal/src/struct_schema.rs
@@ -2,15 +2,15 @@ use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, ToTokens};
 use syn::{Fields, Ident, ItemStruct};
 
-use crate::helpers::{contains_skip, declaration};
+use crate::helpers::{contains_skip, declaration, quote_where_clause};
 
 pub fn process_struct(input: &ItemStruct, cratename: Ident) -> syn::Result<TokenStream2> {
     let name = &input.ident;
     let name_str = name.to_token_stream().to_string();
     let generics = &input.generics;
-    let (impl_generics, ty_generics, _) = generics.split_for_impl();
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
     // Generate function that returns the name of the type.
-    let (declaration, mut where_clause) =
+    let (declaration, mut where_clause_additions) =
         declaration(&name_str, &input.generics, cratename.clone());
 
     // Generate function that returns the schema of required types.
@@ -31,7 +31,7 @@ pub fn process_struct(input: &ItemStruct, cratename: Ident) -> syn::Result<Token
                 add_definitions_recursively_rec.extend(quote! {
                     <#field_type>::add_definitions_recursively(definitions);
                 });
-                where_clause.push(quote! {
+                where_clause_additions.push(quote! {
                     #field_type: #cratename::BorshSchema
                 });
             }
@@ -53,7 +53,7 @@ pub fn process_struct(input: &ItemStruct, cratename: Ident) -> syn::Result<Token
                 add_definitions_recursively_rec.extend(quote! {
                     <#field_type>::add_definitions_recursively(definitions);
                 });
-                where_clause.push(quote! {
+                where_clause_additions.push(quote! {
                     #field_type: #cratename::BorshSchema
                 });
             }
@@ -80,11 +80,7 @@ pub fn process_struct(input: &ItemStruct, cratename: Ident) -> syn::Result<Token
             #add_definitions_recursively_rec
         }
     };
-    let where_clause = if !where_clause.is_empty() {
-        quote! { where #(#where_clause),*}
-    } else {
-        TokenStream2::new()
-    };
+    let where_clause = quote_where_clause(where_clause, where_clause_additions);
     Ok(quote! {
         impl #impl_generics #cratename::BorshSchema for #name #ty_generics #where_clause {
             fn declaration() -> #cratename::schema::Declaration {
@@ -102,7 +98,7 @@ mod tests {
     use super::*;
 
     fn assert_eq(expected: TokenStream2, actual: TokenStream2) {
-        assert_eq!(expected.to_string(), actual.to_string())
+        pretty_assertions::assert_eq!(expected.to_string(), actual.to_string())
     }
 
     #[test]
@@ -345,6 +341,57 @@ mod tests {
         assert_eq(expected, actual);
     }
 
+    #[test]
+    fn trailing_comma_generics() {
+        let item_struct: ItemStruct = syn::parse2(quote!{
+            struct A<K, V>
+            where
+                K: Display + Debug,
+            {
+                x: HashMap<K, V>,
+                y: String,
+            }
+        })
+        .unwrap();
+
+        let actual = process_struct(
+            &item_struct,
+            Ident::new("borsh", proc_macro2::Span::call_site()),
+        )
+        .unwrap();
+        let expected = quote!{
+            impl<K, V> borsh::BorshSchema for A<K, V>
+            where
+                K: Display + Debug,
+                K: borsh::BorshSchema,
+                V: borsh::BorshSchema,
+                HashMap<K, V>: borsh::BorshSchema,
+                String: borsh::BorshSchema
+            {
+                fn declaration() -> borsh::schema::Declaration {
+                    let params = borsh::maybestd::vec![<K>::declaration(), <V>::declaration()];
+                    format!(r#"{}<{}>"#, "A", params.join(", "))
+                }
+                fn add_definitions_recursively(
+                    definitions: &mut borsh::maybestd::collections::HashMap<
+                        borsh::schema::Declaration,
+                        borsh::schema::Definition
+                    >
+                ) {
+                    let fields = borsh::schema::Fields::NamedFields(borsh::maybestd::vec![
+                        ("x".to_string(), <HashMap<K, V> >::declaration()),
+                        ("y".to_string(), <String>::declaration())
+                    ]);
+                    let definition = borsh::schema::Definition::Struct { fields };
+                    Self::add_definition(Self::declaration(), definition, definitions);
+                    <HashMap<K, V> >::add_definitions_recursively(definitions);
+                    <String>::add_definitions_recursively(definitions);
+                }
+            }
+        };
+        assert_eq(expected, actual);
+    }
+
     #[test]
     fn tuple_struct_whole_skip() {
         let item_struct: ItemStruct = syn::parse2(quote!{
diff --git a/borsh/src/de/mod.rs b/borsh/src/de/mod.rs
index 2cf1a0cd..eedf6c87 100644
--- a/borsh/src/de/mod.rs
+++ b/borsh/src/de/mod.rs
@@ -17,7 +17,7 @@ use crate::maybestd::{
 };
 
 #[cfg(feature = "rc")]
-use std::{rc::Rc, sync::Arc};
+use crate::maybestd::{rc::Rc, sync::Arc};
 
 mod hint;
 
diff --git a/borsh/src/schema.rs b/borsh/src/schema.rs
index 14ac4218..80921d0a 100644
--- a/borsh/src/schema.rs
+++ b/borsh/src/schema.rs
@@ -14,7 +14,7 @@
 use crate as borsh; // For `#[derive(BorshSerialize, BorshDeserialize)]`.
 use crate::maybestd::{
     boxed::Box,
-    collections::{hash_map::Entry, HashMap},
+    collections::{hash_map::Entry, HashMap, HashSet},
     format,
     string::{String, ToString},
     vec,
@@ -282,6 +282,23 @@ where
     }
 }
 
+impl<T> BorshSchema for HashSet<T>
+where
+    T: BorshSchema,
+{
+    fn add_definitions_recursively(definitions: &mut HashMap<Declaration, Definition>) {
+        let definition = Definition::Sequence {
+            elements: <T>::declaration(),
+        };
+        Self::add_definition(Self::declaration(), definition, definitions);
+        <T>::add_definitions_recursively(definitions);
+    }
+
+    fn declaration() -> Declaration {
+        format!(r#"HashSet<{}>"#, T::declaration())
+    }
+}
+
 macro_rules! impl_tuple {
     ($($name:ident),+) => {
     impl<$($name),+> BorshSchema for ($($name),+)
@@ -466,6 +483,20 @@ mod tests {
         );
     }
 
+    #[test]
+    fn simple_set() {
+        let actual_name = HashSet::<String>::declaration();
+        let mut actual_defs = map!();
+        HashSet::<String>::add_definitions_recursively(&mut actual_defs);
+        assert_eq!("HashSet<string>", actual_name);
+        assert_eq!(
+            map! {
+                "HashSet<string>" => Definition::Sequence { elements: "string".to_string()}
+            },
+            actual_defs
+        );
+    }
+
     #[test]
     fn simple_array() {
         let actual_name = <[u64; 32]>::declaration();
diff --git a/borsh/src/ser/mod.rs b/borsh/src/ser/mod.rs
index 56b248c3..212d0463 100644
--- a/borsh/src/ser/mod.rs
+++ b/borsh/src/ser/mod.rs
@@ -12,7 +12,7 @@ use crate::maybestd::{
 };
 
 #[cfg(feature = "rc")]
-use std::{rc::Rc, sync::Arc};
+use crate::maybestd::{rc::Rc, sync::Arc};
 
 pub(crate) mod helpers;
 


Test patch:
diff --git a/borsh/tests/test_rc.rs b/borsh/tests/test_rc.rs
index 426a697c..38fc7e30 100644
--- a/borsh/tests/test_rc.rs
+++ b/borsh/tests/test_rc.rs
@@ -1,7 +1,8 @@
 #![cfg(feature = "rc")]
 
+use borsh::maybestd::rc::Rc;
+use borsh::maybestd::sync::Arc;
 use borsh::{BorshDeserialize, BorshSerialize};
-use std::{rc::Rc, sync::Arc};
 
 #[test]
 fn test_rc_roundtrip() {