Repository: JelteF/derive_more
Pull request #238 (2023-02-06 15:33:01)

Fix generics tokenization in `Error` derive macro
## Synopsis

`#[derive(Error)]` on `enum`s or `struct`s that have *both* `const` generics and generic types...
```rust
#[derive(Debug, Display, Error)]
enum ErrConstGeneric<const X: usize, E> {
    Inner(E),
}
```
...panics:
```
error: proc-macro derive panicked
   --> tests/error/derives_for_generic_enums_with_source.rs:270:30
    |
270 |     #[derive(Debug, Display, Error)]
    |                              ^^^^^
    |
    = help: message: expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime
```

## Solution

For some reason, iterating over [`syn::Generics::params`](https://docs.rs/syn/latest/syn/struct.Generics.html#structfield.params) that consists of only `const` generics or only generic types, tokenizes them as identifiers only, but when we mix them, `syn` decides to output `const` generics like they were defined, with `const` keyword, type annotation, etc. So when we are rendering the trait `impl`, adding bounds for an item in `where` clause, we get this:
```rust
#[automatically_derived]
impl<const X: usize, E> ::std::error::Error for ErrConstGeneric<X, E>
where
    E: ::std::fmt::Debug + ::std::fmt::Display + ::std::error::Error + 'static,
    ErrConstGeneric<const X: usize, E>: ::std::fmt::Debug + ::std::fmt::Display, // obv wrong
{
    fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
        match self {
            ErrConstGeneric::Inner(source) => {
                Some(source as &(dyn ::std::error::Error + 'static))
            }
        }
    }
}
```

Use `syn`'s built-in [`Generics::split_for_impl`](https://docs.rs/syn/latest/syn/struct.Generics.html#method.split_for_impl) to obtain a [type](https://docs.rs/syn/latest/syn/struct.TypeGenerics.html) that properly tokenizes all generic parameters.

As for the tests, I've added a "compile fail" tests for `Error` derive macro on generic `enum`s and `struct`s with combinations of different generics. AFAIU, this happens only in `Error` macro, tell me if I should add tests for some others.

This should be listed in the changelog under `Fixes` section, am I right?

## Checklist

- [x] Documentation is updated (if required)
- [x] Tests are added/updated (if required)
- [x] [CHANGELOG entry][l:1] is added (if required)




[l:1]: /CHANGELOG.md
[`syn`]: https://docs.rs/syn

zohnannor: Thank you! I'll make corrections and re-request a review when I can.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5eb6715..46d298c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Use a deterministic `HashSet` in all derives, this is needed for rust analyzer
   to work correctly.
 - Use `Provider` API for backtraces in `Error` derive.
+- Fix `Error` derive not working with `const` generics.
 
 ## 0.99.10 - 2020-09-11
 
diff --git a/impl/src/error.rs b/impl/src/error.rs
index e05a19e..3db73bb 100644
--- a/impl/src/error.rs
+++ b/impl/src/error.rs
@@ -48,20 +48,20 @@ pub fn expand(
     let provide = provide.map(|provide| {
         quote! {
             fn provide<'_demand>(&'_demand self, demand: &mut ::std::any::Demand<'_demand>) {
-                 #provide
-             }
+                #provide
+            }
         }
     });
 
     let mut generics = generics.clone();
 
     if !type_params.is_empty() {
-        let generic_parameters = generics.params.iter();
+        let (_, ty_generics, _) = generics.split_for_impl();
         generics = utils::add_extra_where_clauses(
             &generics,
             quote! {
                 where
-                    #ident<#(#generic_parameters),*>: ::std::fmt::Debug + ::std::fmt::Display
+                    #ident #ty_generics: ::std::fmt::Debug + ::std::fmt::Display
             },
         );
     }


Test patch:
diff --git a/tests/generics.rs b/tests/generics.rs
index 3b48ad9..89934e7 100644
--- a/tests/generics.rs
+++ b/tests/generics.rs
@@ -1,7 +1,7 @@
 #![allow(dead_code, non_camel_case_types)]
 
 use derive_more::{
-    Add, AddAssign, Constructor, Deref, DerefMut, Display, From, FromStr, Index,
+    Add, AddAssign, Constructor, Deref, DerefMut, Display, Error, From, FromStr, Index,
     IndexMut, IntoIterator, Mul, MulAssign, Not, Sum,
 };
 
@@ -77,7 +77,7 @@ struct WrappedWithConst<T, const C: u32>(T);
 #[deref(forward)]
 #[deref_mut(forward)]
 #[into_iterator(owned, ref, ref_mut)]
-struct Struct<T: Clone> {
+struct Struct1<T: Clone> {
     t: T,
 }
 
@@ -124,3 +124,140 @@ enum StructEnum2<T: Clone, U: Clone, X: Clone> {
     DoubleStruct { t: T, u: U },
     TripleStruct { t: T, u: U, x: X },
 }
+
+#[derive(Debug, Display, Error)]
+enum Enum {}
+
+#[derive(Debug, Display, Error)]
+enum EnumGeneric<E> {
+    Inner(E),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumConst<const X: usize> {}
+
+#[derive(Debug, Display, Error)]
+enum EnumConstDefault<const X: usize = 42> {}
+
+#[derive(Debug, Display, Error)]
+enum EnumLifetime<'lt: 'static> {
+    Inner(&'lt Enum),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumConstGeneric<const X: usize, E> {
+    Inner(E),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumGenericConst<E, const X: usize> {
+    Inner(E),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumGenericConstDefault<E, const X: usize = 42> {
+    Inner(E),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumLifetimeGeneric<'lt: 'static, E> {
+    Inner(&'lt E),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumLifetimeConst<'lt: 'static, const X: usize> {
+    Inner(&'lt EnumConst<X>),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumLifetimeConstDefault<'lt: 'static, const X: usize = 42> {
+    Inner(&'lt EnumConst<X>),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumLifetimeConstGeneric<'lt: 'static, const X: usize, E> {
+    Inner(&'lt E),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumLifetimeGenericConst<'lt: 'static, E, const X: usize> {
+    Inner(&'lt E),
+}
+
+#[derive(Debug, Display, Error)]
+enum EnumLifetimeGenericConstDefault<'lt: 'static, E, const X: usize = 42> {
+    Inner(&'lt E),
+}
+
+#[derive(Debug, Display, Error)]
+struct Struct;
+
+#[derive(Debug, Display, Error)]
+struct StructGeneric<E> {
+    inner: E,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructConst<const X: usize> {}
+
+#[derive(Debug, Display, Error)]
+struct StructConstDefault<const X: usize = 42> {}
+
+#[derive(Debug, Display, Error)]
+struct StructLifetime<'lt: 'static> {
+    inner: &'lt Enum,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructConstGeneric<const X: usize, E> {
+    inner: E,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructGenericConst<E, const X: usize> {
+    inner: E,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructGenericConstDefault<E, const X: usize = 42> {
+    inner: E,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructLifetimeGeneric<'lt: 'static, E> {
+    inner: &'lt E,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructLifetimeConst<'lt: 'static, const X: usize> {
+    inner: &'lt EnumConst<X>,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructLifetimeConstDefault<'lt: 'static, const X: usize = 42> {
+    inner: &'lt EnumConst<X>,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructLifetimeConstGeneric<'lt: 'static, const X: usize, E> {
+    inner: &'lt E,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructLifetimeGenericConst<'lt: 'static, E, const X: usize> {
+    inner: &'lt E,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructLifetimeGenericConstDefault<'lt: 'static, E, const X: usize = 42> {
+    inner: &'lt E,
+}
+
+#[derive(Debug, Display, Error)]
+struct StructLifetimeGenericBoundsConstDefault<
+    'lt: 'static,
+    E: Clone,
+    const X: usize = 42,
+> {
+    inner: &'lt E,
+}