1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/// Implements [`Error`] for enums.
///
/// Emitted code is not compatible with `#[no_std]`.
///
/// # Examples
///
/// ```
/// # extern crate alloc;
/// use std::error::Error as _;
///
/// #[derive(Debug)]
/// enum Err {
///     Io(std::io::Error),
///     Generic(String),
/// }
///
/// impl_more::impl_display_enum!(Err, Io(err) => "{err}", Generic(msg) => "{msg}");
/// impl_more::impl_error_enum!(Err, Io(err) => err);
///
/// # let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
/// assert!(Err::Io(io_err).source().is_some());
/// assert!(Err::Generic("oops".to_owned()).source().is_none());
/// ```
///
/// [`Error`]: std::error::Error
#[macro_export]
macro_rules! impl_error_enum {
    ($ty:ty, $($variant:ident ($($inner:ident),+) => $source:expr),+ ,) => {
        impl ::std::error::Error for $ty {
            fn source(&self) -> ::core::option::Option<&(dyn ::std::error::Error + 'static)> {
                match self {
                    $(
                        Self::$variant($($inner),+) => ::core::option::Option::Some($source),
                    )*
                    _ => ::core::option::Option::None,
                }
            }
        }
    };

    ($ty:ty, $($variant:ident ($($inner:ident),+) => $source:expr),+) => {
        $crate::impl_error_enum!($ty, $($variant ($($inner),+) => $source),+ ,);
    };

    ($ty:ty, $($variant:ident { $($inner:ident),+ } => $source:expr),+ ,) => {
        impl ::std::error::Error for $ty {
            fn source(&self) -> ::core::option::Option<&(dyn ::std::error::Error + 'static)> {
                match self {
                    $(
                        Self::$variant($($inner),+) => ::core::option::Option::Some($source),
                    )*
                    _ => ::core::option::Option::None,
                }
            }
        }
    };

    ($ty:ty, $($variant:ident { $($inner:ident),+ } => $source:expr),+) => {
        $crate::impl_error_enum!($ty, $($variant { $($inner),+ } => $source),+ ,);
    };

    ($ty:ty,) => {
        impl ::std::error::Error for $ty {}
    };

    ($ty:ty) => {
        $crate::impl_error_enum!($ty,);
    };
}

#[cfg(test)]
mod tests {
    use alloc::string::String;
    use std::error::Error as _;

    #[test]
    fn with_trailing_comma() {
        #![allow(unused)]

        #[derive(Debug)]
        enum Foo {
            Bar,
        }

        impl_display_enum!(Foo, Bar => "bar");
        impl_error_enum!(Foo,);
    }

    #[test]
    fn no_inner_data() {
        #[derive(Debug)]
        enum Foo {
            Bar,
            Baz,
        }

        impl_display_enum!(Foo, Bar => "bar", Baz => "qux");
        impl_error_enum!(Foo);

        assert!(Foo::Bar.source().is_none());
        assert!(Foo::Baz.source().is_none());
    }

    #[test]
    fn uniform_enum() {
        #[derive(Debug)]
        enum Foo {
            Bar(String),
            Baz(std::io::Error),
            Qux(String, std::io::Error),
        }

        impl_display_enum!(
            Foo,
            Bar(desc) => "{desc}",
            Baz(err) => "{err}",
            Qux(desc, err) => "{desc}: {err}"
        );
        impl_error_enum!(Foo, Baz(err) => err, Qux(_desc, err) => err);

        assert!(Foo::Bar(String::new()).source().is_none());

        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
        assert!(Foo::Baz(io_err).source().is_some());

        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
        assert!(Foo::Qux(String::new(), io_err).source().is_some());
    }
}