Repository: shepmaster/snafu (rust)

Issue:
Const generics seem to be giving the derive macro trouble
I'm moving one of my libs over to snafu but I've run into an issue with an error that uses const generics. It compiles if I remove the `Snafu` derive.

```rust
#[derive(Debug, Snafu)]
#[snafu(display(
    "the length of a string or slice overflows the maximum of {}, received {}",
    Self::M,
    value
))]
pub struct OverflowError<Value, const M: u64 = { u32::MAX as u64 }> {
    value: Value,
}
```


```
error[E0412]: cannot find type `M` in this scope
 --> src/main.rs:8:39
  |
8 | pub struct OverflowError<Value, const M: u64 = { u32::MAX as u64 }> {
  |                                       ^ not found in this scope
  |
help: you might be missing a type parameter
  |
8 | pub struct OverflowError<Value, M, const M: u64 = { u32::MAX as u64 }> {
  |                               +++

error[E0412]: cannot find type `M` in this scope
 --> src/main.rs:8:39
  |
8 | pub struct OverflowError<Value, const M: u64 = { u32::MAX as u64 }> {
  |                                       ^ not found in this scope

error[E0747]: unresolved item provided when a constant was expected
 --> src/main.rs:8:39
  |
8 | pub struct OverflowError<Value, const M: u64 = { u32::MAX as u64 }> {
  |                                       ^
  |
help: if this generic argument was intended as a const parameter, surround it with braces
  |
8 | pub struct OverflowError<Value, const { M }: u64 = { u32::MAX as u64 }> {
  |                                       +   +

Some errors have detailed explanations: E0412, E0747.
For more information about an error, try `rustc --explain E0412`.
error: could not compile `rust` (bin "rust") due to 9 previous errors
```

Pull request:
Support errors with const generics
Fixes #436

Patch:
diff --git a/snafu-derive/src/lib.rs b/snafu-derive/src/lib.rs
index ecc5e61..2671f66 100644
--- a/snafu-derive/src/lib.rs
+++ b/snafu-derive/src/lib.rs
@@ -1496,9 +1496,10 @@ trait GenericAwareNames {
 
     fn provided_generic_types_without_defaults(&self) -> Vec<proc_macro2::TokenStream> {
         use syn::TypeParam;
+
         self.generics()
             .type_params()
-            .map(|t: &TypeParam| {
+            .map(|t| {
                 let TypeParam {
                     attrs,
                     ident,
@@ -1520,20 +1521,46 @@ trait GenericAwareNames {
         self.provided_generic_lifetimes()
             .into_iter()
             .chain(self.provided_generic_types_without_defaults())
+            .chain(self.provided_generic_consts_without_defaults())
             .collect()
     }
 
     fn provided_generic_lifetimes(&self) -> Vec<proc_macro2::TokenStream> {
-        use syn::{GenericParam, LifetimeParam};
+        use syn::LifetimeParam;
 
         self.generics()
-            .params
-            .iter()
-            .flat_map(|p| match p {
-                GenericParam::Lifetime(LifetimeParam { lifetime, .. }) => {
-                    Some(quote! { #lifetime })
+            .lifetimes()
+            .map(|l| {
+                let LifetimeParam {
+                    attrs, lifetime, ..
+                } = l;
+                quote! {
+                    #(#attrs)*
+                    #lifetime
+                }
+            })
+            .collect()
+    }
+
+    fn provided_generic_consts_without_defaults(&self) -> Vec<proc_macro2::TokenStream> {
+        self.generics()
+            .const_params()
+            .map(|c| {
+                let syn::ConstParam {
+                    attrs,
+                    const_token,
+                    ident,
+                    colon_token,
+                    ty,
+                    ..
+                } = c;
+                quote! {
+                    #(#attrs)*
+                    #const_token
+                    #ident
+                    #colon_token
+                    #ty
                 }
-                _ => None,
             })
             .collect()
     }


Test patch:
diff --git a/tests/generics.rs b/tests/generics.rs
index d1adf45..e9fe817 100644
--- a/tests/generics.rs
+++ b/tests/generics.rs
@@ -98,3 +98,73 @@ mod bounds {
         }
     }
 }
+
+mod const_generics {
+    use snafu::prelude::*;
+
+    #[derive(Debug, Snafu)]
+    #[snafu(display("Exceeded {N}"))]
+    pub struct Error<const N: i32>;
+
+    #[test]
+    fn implements_error() {
+        fn check_bounds<T: std::error::Error>() {}
+        check_bounds::<Error<1>>();
+        check_bounds::<Error<2>>();
+    }
+
+    #[test]
+    fn can_be_constructed() {
+        fn make_one() -> Result<(), Error<1>> {
+            Snafu.fail()
+        }
+
+        fn make_two() -> Result<(), Error<2>> {
+            Snafu.fail()
+        }
+
+        assert!(make_one().is_err());
+        assert!(make_two().is_err());
+    }
+
+    #[test]
+    fn can_use_const_in_display() {
+        let e: Error<42> = Snafu.build();
+        assert_eq!(e.to_string(), "Exceeded 42");
+    }
+
+    mod with_default {
+        use snafu::prelude::*;
+
+        #[derive(Debug, Snafu)]
+        #[snafu(display("Exceeded {N}"))]
+        pub struct Error<const N: i32 = 42>;
+
+        #[test]
+        fn implements_error() {
+            fn check_bounds<T: std::error::Error>() {}
+            check_bounds::<Error>();
+            check_bounds::<Error<99>>();
+        }
+
+        #[test]
+        fn can_be_constructed() {
+            fn make_forty_two() -> Result<(), Error> {
+                Snafu.fail()
+            }
+
+            fn make_ninety_nine() -> Result<(), Error<99>> {
+                Snafu.fail()
+            }
+
+            assert!(make_forty_two().is_err());
+            assert!(make_ninety_nine().is_err());
+        }
+
+        #[test]
+        fn can_use_const_in_display() {
+            let e: Error = Snafu.build();
+            assert_eq!(e.to_string(), "Exceeded 42");
+        }
+    }
+}