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
```


Discussion:
A more minimal example
```rs
#[derive(Snafu)]
pub struct Foo<const N: i32> {}
```
This code does not compile with the derive macro present, with the following error.
If you take away the derive macro, it compiles normally

```rs
error[E0412]: cannot find type `N` in this scope
 --> src/main.rs:4:22
  |
4 | pub struct Foo<const N: i32> {}
  |                      ^ not found in this scope

error[E0747]: unresolved item provided when a constant was expected
 --> src/main.rs:4:22
  |
4 | pub struct Foo<const N: i32> {}
  |                      ^
  |
help: if this generic argument was intended as a const parameter, surround it with braces
  |
4 | pub struct Foo<const { N }: i32> {}
  |                      +   +
```
See #438 



Patch:
diff --git a/snafu-derive/src/lib.rs b/snafu-derive/src/lib.rs
index ecc5e61d..2671f666 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 d1adf454..e9fe8178 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");
+        }
+    }
+}