Skip to content

Commit

Permalink
Fix generics tokenization in Error derive macro (#238)
Browse files Browse the repository at this point in the history
## 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.

Co-authored-by: tyranron <[email protected]>
  • Loading branch information
zohnannor and tyranron authored Feb 7, 2023
1 parent 273b947 commit 4816fd9
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions impl/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
);
}
Expand Down
141 changes: 139 additions & 2 deletions tests/generics.rs
Original file line number Diff line number Diff line change
@@ -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,
};

Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -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,
}

0 comments on commit 4816fd9

Please sign in to comment.