Repository: shepmaster/snafu (rust)

Issue:
Inline trait bounds from error types should not be included in struct names
[@mikeando points out](https://github.com/shepmaster/snafu/pull/54#issuecomment-486034916) that we don't preserve user-provided trait bounds when they are provided inline:

```rust
#[derive(Debug, Snafu)]
enum InlineBoundsError<T: Display> {
    #[snafu(display("Boom: {}", value))]
    Boom { value: T },
}
```

Mirror issue of #53 

Pull request:
Do not include inline trait bounds in struct names
Fixes #55

Patch:
diff --git a/snafu-derive/src/lib.rs b/snafu-derive/src/lib.rs
index b610868..16385bf 100644
--- a/snafu-derive/src/lib.rs
+++ b/snafu-derive/src/lib.rs
@@ -429,6 +429,41 @@ impl From<StructInfo> for proc_macro::TokenStream {
     }
 }
 
+trait GenericAwareNames {
+    fn name(&self) -> &syn::Ident;
+
+    fn generics(&self) -> &syn::Generics;
+
+    fn parameterized_name(&self) -> UserInput {
+        let enum_name = self.name();
+        let original_generics = self.provided_generic_names();
+
+        Box::new(quote! { #enum_name<#(#original_generics,)*> })
+    }
+
+    fn provided_generic_names(&self) -> Vec<proc_macro2::TokenStream> {
+        use syn::{ConstParam, GenericParam, LifetimeDef, TypeParam};
+
+        self.generics()
+            .params
+            .iter()
+            .map(|p| match *p {
+                GenericParam::Type(TypeParam { ref ident, .. }) => quote! { #ident },
+                GenericParam::Lifetime(LifetimeDef { ref lifetime, .. }) => quote! { #lifetime },
+                GenericParam::Const(ConstParam { ref ident, .. }) => quote! { #ident },
+            })
+            .collect()
+    }
+
+    fn provided_where_clauses(&self) -> Vec<proc_macro2::TokenStream> {
+        self.generics()
+            .where_clause
+            .iter()
+            .flat_map(|c| c.predicates.iter().map(|p| quote! { #p }))
+            .collect()
+    }
+}
+
 impl EnumInfo {
     fn generate_snafu(self) -> proc_macro2::TokenStream {
         let context_selectors = ContextSelectors(&self);
@@ -443,20 +478,15 @@ impl EnumInfo {
             #error_compat_impl
         }
     }
+}
 
-    fn parameterized_enum_name(&self) -> UserInput {
-        let enum_name = &self.name;
-        let original_generics = self.generics.params.iter();
-
-        Box::new(quote! { #enum_name<#(#original_generics,)*> })
+impl GenericAwareNames for EnumInfo {
+    fn name(&self) -> &syn::Ident {
+        &self.name
     }
 
-    fn provided_where_clauses(&self) -> Vec<proc_macro2::TokenStream> {
-        self.generics
-            .where_clause
-            .iter()
-            .flat_map(|c| c.predicates.iter().map(|p| quote! { #p }))
-            .collect()
+    fn generics(&self) -> &syn::Generics {
+        &self.generics
     }
 }
 
@@ -488,7 +518,7 @@ impl<'a> quote::ToTokens for ContextSelector<'a> {
         let enum_name = &self.0.name;
         let original_generics: &Vec<_> = &self.0.generics.params.iter().collect();
 
-        let parameterized_enum_name = &self.0.parameterized_enum_name();
+        let parameterized_enum_name = &self.0.parameterized_name();
 
         let VariantInfo {
             name: ref variant_name,
@@ -663,7 +693,7 @@ impl<'a> DisplayImpl<'a> {
 impl<'a> quote::ToTokens for DisplayImpl<'a> {
     fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
         let original_generics = &self.0.generics;
-        let parameterized_enum_name = &self.0.parameterized_enum_name();
+        let parameterized_enum_name = &self.0.parameterized_name();
         let where_clauses = &self.0.provided_where_clauses();
 
         let variants_to_display = &self.variants_to_display();
@@ -744,7 +774,7 @@ impl<'a> ErrorImpl<'a> {
 impl<'a> quote::ToTokens for ErrorImpl<'a> {
     fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
         let original_generics = &self.0.generics;
-        let parameterized_enum_name = &self.0.parameterized_enum_name();
+        let parameterized_enum_name = &self.0.parameterized_name();
         let where_clauses: &Vec<_> = &self.0.provided_where_clauses();
 
         let variants_to_description = &self.variants_to_description();
@@ -838,7 +868,7 @@ impl<'a> ErrorCompatImpl<'a> {
 impl<'a> quote::ToTokens for ErrorCompatImpl<'a> {
     fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
         let original_generics = &self.0.generics;
-        let parameterized_enum_name = &self.0.parameterized_enum_name();
+        let parameterized_enum_name = &self.0.parameterized_name();
         let where_clauses = &self.0.provided_where_clauses();
 
         let variants = &self.variants_to_backtrace();
@@ -870,13 +900,14 @@ impl<'a> quote::ToTokens for ErrorCompatImpl<'a> {
 
 impl StructInfo {
     fn generate_snafu(self) -> proc_macro2::TokenStream {
+        let parameterized_struct_name = self.parameterized_name();
+
         let StructInfo {
             inner_type,
             generics,
             name,
         } = self;
 
-        let parameterized_struct_name = quote! { #name#generics };
         let where_clauses: &Vec<_> = &generics
             .where_clause
             .iter()
@@ -966,6 +997,16 @@ impl StructInfo {
     }
 }
 
+impl GenericAwareNames for StructInfo {
+    fn name(&self) -> &syn::Ident {
+        &self.name
+    }
+
+    fn generics(&self) -> &syn::Generics {
+        &self.generics
+    }
+}
+
 trait Transpose<T, E> {
     fn my_transpose(self) -> Result<Option<T>, E>;
 }


Test patch:
diff --git a/tests/generics.rs b/tests/generics.rs
index b942904..3d45d98 100644
--- a/tests/generics.rs
+++ b/tests/generics.rs
@@ -5,16 +5,7 @@ use snafu::{Backtrace, ResultExt, Snafu};
 type BoxError = Box<std::error::Error>;
 
 #[derive(Debug, Snafu)]
-struct ApiError<'a, 'x, A, Y>(Error<'a, 'x, A, Y>)
-where
-    A: std::fmt::Debug,
-    Y: std::fmt::Debug;
-
-#[derive(Debug, Snafu)]
-enum Error<'a, 'x, A, Y>
-where
-    Y: std::fmt::Debug,
-{
+enum Error<'a, 'x, A, Y> {
     Everything {
         source: BoxError,
         name: &'a str,
@@ -24,7 +15,6 @@ where
     Lifetime {
         key: &'x i32,
     },
-    #[snafu(display("Type: {:?}", value))]
     Type {
         value: Y,
     },
@@ -61,3 +51,52 @@ fn implements_error() {
 
     example(&name, &key, value).unwrap();
 }
+
+mod bounds {
+    mod inline {
+        use snafu::Snafu;
+        use std::fmt::{Debug, Display};
+
+        #[derive(Debug, Snafu)]
+        pub struct ApiError<T: Debug + Display>(Error<T>);
+
+        #[derive(Debug, Snafu)]
+        enum Error<T: Display> {
+            #[snafu(display("Boom: {}", value))]
+            Boom { value: T },
+        }
+
+        #[test]
+        fn implements_error() {
+            fn check_bounds<T: std::error::Error>() {}
+            check_bounds::<Error<i32>>();
+            check_bounds::<ApiError<i32>>();
+        }
+    }
+
+    mod where_clause {
+        use snafu::Snafu;
+        use std::fmt::{Debug, Display};
+
+        #[derive(Debug, Snafu)]
+        pub struct ApiError<T>(Error<T>)
+        where
+            T: Debug + Display;
+
+        #[derive(Debug, Snafu)]
+        enum Error<T>
+        where
+            T: Display,
+        {
+            #[snafu(display("Boom: {}", value))]
+            Boom { value: T },
+        }
+
+        #[test]
+        fn implements_error() {
+            fn check_bounds<T: std::error::Error>() {}
+            check_bounds::<Error<i32>>();
+            check_bounds::<ApiError<i32>>();
+        }
+    }
+}