Repository: near/borsh-rs
Pull request #87 (2022-03-16 20:11:51)

fixes maybstd imports for no_std on thumbv6m-none-eabi
I'm having a tiny bit of trouble getting my Cargo workspace to completely work with patches, paths, a 0.0.0 version for this library and a few other build dep things, but I think this fixes the inability to use `borsh` in `no_std` b/c a failure to `use alloc::sync` on line 32. 

The remainder of the changes are just in keeping with the rest of the codebase, which only depends on `rc` and `sync` when `cfg(feature = "rc")`

austinabell: `alloc::sync` does exist, so not sure what could be the issue there, but I did notice that `rc` was broken when used without `std` feature. I fixed that specific problem with #88. Were you using this with std feature not set and rc set? If not, can you produce a repro of this error or more details?


sunny-g: Hmmm, this could be unique to my env. I'm currently blocked just adding `borsh` as a dependency to the sample Ledger Nano S [rust app](https://github.com/LedgerHQ/rust-app).

Cargo.toml (simplified):
```
[dependencies]
borsh = { version = "0.9", default-features = false }
nanos_sdk = { git = "https://github.com/LedgerHQ/ledger-nanos-sdk" }
nanos_ui = { git = "https://github.com/LedgerHQ/ledger-nanos-ui" }
```

and this is the error produced: 
```
> cargo build
    Updating crates.io index
   Compiling borsh-derive-internal v0.9.3
   Compiling borsh-schema-derive-internal v0.9.3
   Compiling borsh-derive v0.9.3
   Compiling borsh v0.9.3
error[E0432]: unresolved import `alloc::sync`
  --> /home/sunnyg/.cargo/registry/src/github.com-1ecc6299db9ec823/borsh-0.9.3/src/lib.rs:32:56
   |
32 |     pub use alloc::{borrow, boxed, format, rc, string, sync, vec};
   |                                                        ^^^^ no `sync` in the root

For more information about this error, try `rustc --explain E0432`.
error: could not compile `borsh` due to previous error
```
sunny-g: But to answer your questions, as you can see above I've disabled all features, under the assumption that you couldn't use `rc` without `std`. If that's the fix, then my bad!
sunny-g: So that's not the fix - I'm getting substantially more errors, including the aforementioned one. The target is `thumbv6m-none-eabi` - is that the culprit? I'm not too familiar with embedded rust.

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..f4b2d940 100644
--- a/borsh/src/de/mod.rs
+++ b/borsh/src/de/mod.rs
@@ -17,12 +17,13 @@ use crate::maybestd::{
 };
 
 #[cfg(feature = "rc")]
-use std::{rc::Rc, sync::Arc};
+use crate::maybestd::{rc::Rc, sync::Arc};
 
 mod hint;
 
 const ERROR_NOT_ALL_BYTES_READ: &str = "Not all bytes read";
 const ERROR_UNEXPECTED_LENGTH_OF_INPUT: &str = "Unexpected length of input";
+const ERROR_OVERFLOW_ON_MACHINE_WITH_32_BIT_ISIZE: &str = "Overflow on machine with 32 bit isize";
 const ERROR_OVERFLOW_ON_MACHINE_WITH_32_BIT_USIZE: &str = "Overflow on machine with 32 bit usize";
 const ERROR_INVALID_ZERO_VALUE: &str = "Expected a non-zero value";
 
@@ -157,6 +158,19 @@ impl_for_nonzero_integer!(core::num::NonZeroU64);
 impl_for_nonzero_integer!(core::num::NonZeroU128);
 impl_for_nonzero_integer!(core::num::NonZeroUsize);
 
+impl BorshDeserialize for isize {
+    fn deserialize(buf: &mut &[u8]) -> Result<Self> {
+        let i: i64 = BorshDeserialize::deserialize(buf)?;
+        let i = isize::try_from(i).map_err(|_| {
+            Error::new(
+                ErrorKind::InvalidInput,
+                ERROR_OVERFLOW_ON_MACHINE_WITH_32_BIT_ISIZE,
+            )
+        })?;
+        Ok(i)
+    }
+}
+
 impl BorshDeserialize for usize {
     fn deserialize(buf: &mut &[u8]) -> Result<Self> {
         let u: u64 = BorshDeserialize::deserialize(buf)?;
@@ -574,7 +588,7 @@ impl BorshDeserialize for () {
 
 macro_rules! impl_tuple {
     ($($name:ident)+) => {
-      impl<$($name),+> BorshDeserialize for ($($name),+)
+      impl<$($name),+> BorshDeserialize for ($($name,)+)
       where $($name: BorshDeserialize,)+
       {
         #[inline]
@@ -586,6 +600,7 @@ macro_rules! impl_tuple {
     };
 }
 
+impl_tuple!(T0);
 impl_tuple!(T0 T1);
 impl_tuple!(T0 T1 T2);
 impl_tuple!(T0 T1 T2 T3);
diff --git a/borsh/src/lib.rs b/borsh/src/lib.rs
index 6a13270d..8db27c42 100644
--- a/borsh/src/lib.rs
+++ b/borsh/src/lib.rs
@@ -21,7 +21,10 @@ pub use ser::BorshSerialize;
 /// module.
 #[cfg(feature = "std")]
 pub mod maybestd {
-    pub use std::{borrow, boxed, collections, format, io, rc, string, sync, vec};
+    pub use std::{borrow, boxed, collections, format, io, string, vec};
+
+    #[cfg(feature = "rc")]
+    pub use std::{rc, sync};
 }
 
 #[cfg(not(feature = "std"))]
@@ -29,7 +32,10 @@ mod nostd_io;
 
 #[cfg(not(feature = "std"))]
 pub mod maybestd {
-    pub use alloc::{borrow, boxed, format, rc, string, sync, vec};
+    pub use alloc::{borrow, boxed, format, string, vec};
+
+    #[cfg(feature = "rc")]
+    pub use alloc::{rc, sync};
 
     pub mod collections {
         pub use alloc::collections::{BTreeMap, BTreeSet, BinaryHeap, LinkedList, VecDeque};
@@ -39,10 +45,4 @@ pub mod maybestd {
     pub mod io {
         pub use super::super::nostd_io::*;
     }
-
-    pub use hashbrown::{HashMap, HashSet};
-
-    pub mod hash_map {
-        pub use hashbrown::hash_map::Entry;
-    }
 }
diff --git a/borsh/src/schema.rs b/borsh/src/schema.rs
index 14ac4218..f0ea4db0 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,
@@ -29,7 +29,7 @@ pub type VariantName = String;
 /// The name of the field in the struct (can be used to convert JSON to Borsh using the schema).
 pub type FieldName = String;
 /// The type that we use to represent the definition of the Borsh type.
-#[derive(PartialEq, Debug, BorshSerialize, BorshDeserialize, BorshSchemaMacro)]
+#[derive(Clone, PartialEq, Debug, BorshSerialize, BorshDeserialize, BorshSchemaMacro)]
 pub enum Definition {
     /// A fixed-size array with the length known at the compile time and the same-type elements.
     Array { length: u32, elements: Declaration },
@@ -47,7 +47,7 @@ pub enum Definition {
 }
 
 /// The collection representing the fields of a struct.
-#[derive(PartialEq, Debug, BorshSerialize, BorshDeserialize, BorshSchemaMacro)]
+#[derive(Clone, PartialEq, Debug, BorshSerialize, BorshDeserialize, BorshSchemaMacro)]
 pub enum Fields {
     /// The struct with named fields.
     NamedFields(Vec<(FieldName, Declaration)>),
@@ -58,7 +58,7 @@ pub enum Fields {
 }
 
 /// All schema information needed to deserialize a single type.
-#[derive(PartialEq, Debug, BorshSerialize, BorshDeserialize, BorshSchemaMacro)]
+#[derive(Clone, PartialEq, Debug, BorshSerialize, BorshDeserialize, BorshSchemaMacro)]
 pub struct BorshSchemaContainer {
     /// Declaration of the type.
     pub declaration: Declaration,
@@ -142,9 +142,11 @@ macro_rules! impl_for_primitives {
     };
 }
 
-impl_for_primitives!(bool char f32 f64 i8 i16 i32 i64 i128 u8 u16 u32 u64 u128);
+impl_for_primitives!(bool f32 f64 i8 i16 i32 i64 i128 u8 u16 u32 u64 u128);
 impl_for_renamed_primitives!(String: string);
 impl_for_renamed_primitives!(str: string);
+impl_for_renamed_primitives!(isize: i64);
+impl_for_renamed_primitives!(usize: u64);
 
 #[cfg(not(feature = "const-generics"))]
 const _: () = {
@@ -282,9 +284,26 @@ 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),+)
+    impl<$($name),+> BorshSchema for ($($name,)+)
     where
         $($name: BorshSchema),+
     {
@@ -306,6 +325,7 @@ macro_rules! impl_tuple {
     };
 }
 
+impl_tuple!(T0);
 impl_tuple!(T0, T1);
 impl_tuple!(T0, T1, T2);
 impl_tuple!(T0, T1, T2, T3);
@@ -466,6 +486,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..b3244c00 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;
 
@@ -118,6 +118,12 @@ impl_for_nonzero_integer!(core::num::NonZeroU64);
 impl_for_nonzero_integer!(core::num::NonZeroU128);
 impl_for_nonzero_integer!(core::num::NonZeroUsize);
 
+impl BorshSerialize for isize {
+    fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
+        BorshSerialize::serialize(&(*self as i64), writer)
+    }
+}
+
 impl BorshSerialize for usize {
     fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
         BorshSerialize::serialize(&(*self as u64), writer)
@@ -505,7 +511,7 @@ impl BorshSerialize for () {
 
 macro_rules! impl_tuple {
     ($($idx:tt $name:ident)+) => {
-      impl<$($name),+> BorshSerialize for ($($name),+)
+      impl<$($name),+> BorshSerialize for ($($name,)+)
       where $($name: BorshSerialize,)+
       {
         #[inline]
@@ -517,6 +523,7 @@ macro_rules! impl_tuple {
     };
 }
 
+impl_tuple!(0 T0);
 impl_tuple!(0 T0 1 T1);
 impl_tuple!(0 T0 1 T1 2 T2);
 impl_tuple!(0 T0 1 T1 2 T2 3 T3);


Test patch:
diff --git a/borsh/tests/test_primitives.rs b/borsh/tests/test_primitives.rs
new file mode 100644
index 00000000..df3dd274
--- /dev/null
+++ b/borsh/tests/test_primitives.rs
@@ -0,0 +1,22 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+
+macro_rules! test_primitive {
+    ($test_name: ident, $v: expr, $t: ty) => {
+        #[test]
+        fn $test_name() {
+            let expected: $t = $v;
+            let buf = expected.try_to_vec().unwrap();
+            let actual = <$t>::try_from_slice(&buf).expect("failed to deserialize");
+            assert_eq!(actual, expected);
+        }
+    };
+}
+
+test_primitive!(test_isize_neg, -100isize, isize);
+test_primitive!(test_isize_pos, 100isize, isize);
+test_primitive!(test_isize_min, isize::min_value(), isize);
+test_primitive!(test_isize_max, isize::max_value(), isize);
+
+test_primitive!(test_usize, 100usize, usize);
+test_primitive!(test_usize_min, usize::min_value(), usize);
+test_primitive!(test_usize_max, usize::max_value(), usize);
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() {
diff --git a/borsh/tests/test_schema_primitives.rs b/borsh/tests/test_schema_primitives.rs
new file mode 100644
index 00000000..bacaf1e6
--- /dev/null
+++ b/borsh/tests/test_schema_primitives.rs
@@ -0,0 +1,25 @@
+use borsh::schema::*;
+
+#[test]
+fn isize_schema() {
+    let schema = isize::schema_container();
+    assert_eq!(
+        schema,
+        BorshSchemaContainer {
+            declaration: "i64".to_string(),
+            definitions: Default::default()
+        }
+    )
+}
+
+#[test]
+fn usize_schema() {
+    let schema = usize::schema_container();
+    assert_eq!(
+        schema,
+        BorshSchemaContainer {
+            declaration: "u64".to_string(),
+            definitions: Default::default()
+        }
+    )
+}
diff --git a/borsh/tests/test_schema_tuple.rs b/borsh/tests/test_schema_tuple.rs
new file mode 100644
index 00000000..c5e6367a
--- /dev/null
+++ b/borsh/tests/test_schema_tuple.rs
@@ -0,0 +1,28 @@
+use borsh::maybestd::collections::HashMap;
+use borsh::schema::*;
+
+macro_rules! map(
+    () => { HashMap::new() };
+    { $($key:expr => $value:expr),+ } => {
+        {
+            let mut m = HashMap::new();
+            $(
+                m.insert($key.to_string(), $value);
+            )+
+            m
+        }
+     };
+);
+
+#[test]
+fn test_unary_tuple_schema() {
+    assert_eq!("Tuple<bool>", <(bool,)>::declaration());
+    let mut defs = Default::default();
+    <(bool,)>::add_definitions_recursively(&mut defs);
+    assert_eq!(
+        map! {
+        "Tuple<bool>" => Definition::Tuple { elements: vec!["bool".to_string()] }
+        },
+        defs
+    );
+}
diff --git a/borsh/tests/test_tuple.rs b/borsh/tests/test_tuple.rs
new file mode 100644
index 00000000..c629dcfc
--- /dev/null
+++ b/borsh/tests/test_tuple.rs
@@ -0,0 +1,9 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+
+#[test]
+fn test_unary_tuple() {
+    let expected = (true,);
+    let buf = expected.try_to_vec().unwrap();
+    let actual = <(bool,)>::try_from_slice(&buf).expect("failed to deserialize");
+    assert_eq!(actual, expected);
+}