Skip to main content

impl_more/
enum_from.rs

1/// Implement [`From`] for an enum variant with one named or unnamed field.
2///
3/// The destination is the enum path followed by the variant name. Each source
4/// type can have only one conversion into a given enum. Generic conversions must
5/// not overlap with other conversions for any choice of type arguments.
6///
7/// Declare type and const parameters before `in`.
8///
9/// # Examples
10///
11/// With an error enum:
12/// ```
13/// enum Error {
14///     Io(std::io::Error),
15/// }
16///
17/// impl_more::impl_enum_from!(std::io::Error => Error::Io);
18///
19/// fn read() -> Result<String, Error> {
20///     Ok(std::fs::read_to_string("config.txt")?)
21/// }
22/// ```
23///
24/// With a named field:
25/// ```
26/// enum Error {
27///     Io { source: std::io::Error },
28/// }
29///
30/// impl_more::impl_enum_from!(std::io::Error => Error::Io { source });
31///
32/// let error = Error::from(std::io::Error::from(std::io::ErrorKind::NotFound));
33/// assert!(matches!(error, Error::Io { source } if source.kind() == std::io::ErrorKind::NotFound));
34/// ```
35///
36/// With a generic enum and a module path:
37/// ```
38/// mod errors {
39///     pub enum Error<T> {
40///         Io(std::io::Error),
41///         Other(Vec<T>),
42///     }
43/// }
44///
45/// impl_more::impl_enum_from!(<T> in std::io::Error => errors::Error<T>::Io);
46/// impl_more::impl_enum_from!(<T> in Vec<T> => errors::Error<T>::Other);
47///
48/// let error = errors::Error::from(vec![42_u32]);
49/// assert!(matches!(error, errors::Error::Other(values) if values == [42]));
50/// ```
51///
52/// With type and const parameters:
53/// ```
54/// enum Value<T, const N: usize> {
55///     Items([T; N]),
56/// }
57///
58/// impl_more::impl_enum_from!(<T, const N: usize> in [T; N] => Value<T, N>::Items);
59///
60/// let value = Value::from([1, 2, 3]);
61/// assert!(matches!(value, Value::Items([1, 2, 3])));
62/// ```
63#[macro_export]
64macro_rules! impl_enum_from {
65    (<$($generic:ident),+> in $($rest:tt)+) => {
66        $crate::impl_enum_from!(@impl [$($generic),+] $($rest)+);
67    };
68
69    (<const $($rest:tt)+) => {
70        $crate::__impl_more_parse_generics!(@parse impl_enum_from [] [] const $($rest)+);
71    };
72
73    (<$generic:ident, $($rest:tt)+) => {
74        $crate::__impl_more_parse_generics!(@parse impl_enum_from [] [] $generic, $($rest)+);
75    };
76
77    (@impl [$($generic:tt)+] $from:ty => $($target:tt)+) => {
78        $crate::impl_enum_from!(@parse [<$($generic)+>] [$from] [] $($target)+);
79    };
80
81    ($from:ty => $($target:tt)+) => {
82        $crate::impl_enum_from!(@parse [] [$from] [] $($target)+);
83    };
84
85    (@parse [$($generic:tt)*] [$from:ty] [$($enum:tt)+]
86        :: $variant:ident $(,)?) => {
87        impl $($generic)* ::core::convert::From<$from>
88            for $($enum)+
89        {
90            fn from(value: $from) -> Self {
91                Self::$variant(value)
92            }
93        }
94    };
95
96    (@parse [$($generic:tt)*] [$from:ty] [$($enum:tt)+]
97        :: $variant:ident { $field:ident $(,)? } $(,)?) => {
98        impl $($generic)* ::core::convert::From<$from>
99            for $($enum)+
100        {
101            fn from(value: $from) -> Self {
102                Self::$variant { $field: value }
103            }
104        }
105    };
106
107    (@parse [$($generic:tt)*] [$from:ty] [$($path:tt)*]
108        $next:tt $($rest:tt)+) => {
109        $crate::impl_enum_from!(@parse [$($generic)*] [$from]
110            [$($path)* $next] $($rest)+);
111    };
112}
113
114#[cfg(test)]
115mod tests {
116    #[derive(Debug, PartialEq)]
117    enum Value<T> {
118        Some(T),
119    }
120
121    impl_enum_from!(u32 => crate::enum_from::tests::Value<u32>::Some);
122
123    #[test]
124    fn error_conversion_with_question_mark() {
125        enum Error {
126            Io(std::io::Error),
127            Parse(core::num::ParseIntError),
128        }
129
130        impl_enum_from!(std::io::Error => Error::Io);
131        impl_enum_from!(core::num::ParseIntError => Error::Parse,);
132
133        fn fail() -> Result<(), Error> {
134            Err(std::io::Error::from(std::io::ErrorKind::NotFound))?;
135            Ok(())
136        }
137
138        match fail() {
139            Err(Error::Io(error)) => assert_eq!(error.kind(), std::io::ErrorKind::NotFound),
140            _ => panic!("expected an IO error"),
141        }
142
143        let source = "invalid".parse::<u32>().unwrap_err();
144        match Error::from(source.clone()) {
145            Error::Parse(error) => assert_eq!(error, source),
146            _ => panic!("expected a parse error"),
147        }
148    }
149
150    #[test]
151    fn generic_enum_with_module_path() {
152        mod errors {
153            #[derive(Debug, PartialEq)]
154            pub enum Error<T, U> {
155                Code(u32),
156                Other((T, U)),
157            }
158        }
159
160        impl_enum_from!(<T, U> in u32 => errors::Error<T, U>::Code);
161        impl_enum_from!(<T, U> in (T, U) => errors::Error<T, U>::Other,);
162
163        assert_eq!(errors::Error::<u8, bool>::from(42), errors::Error::Code(42));
164        assert_eq!(
165            errors::Error::from((7_u8, true)),
166            errors::Error::Other((7, true))
167        );
168    }
169
170    #[test]
171    fn named_error_conversion_with_question_mark() {
172        enum Error {
173            Io { source: std::io::Error },
174        }
175
176        impl_enum_from!(std::io::Error => Error::Io { source });
177
178        fn fail() -> Result<(), Error> {
179            Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied))?;
180            Ok(())
181        }
182
183        match fail() {
184            Err(Error::Io { source }) => {
185                assert_eq!(source.kind(), std::io::ErrorKind::PermissionDenied);
186            }
187            _ => panic!("expected an IO error"),
188        }
189    }
190
191    #[test]
192    fn named_generic_enum_with_module_path() {
193        mod errors {
194            #[derive(Debug, PartialEq)]
195            pub enum Error<T> {
196                Other { source: T },
197            }
198        }
199
200        impl_enum_from!(<T> in T => errors::Error<T>::Other { source, },);
201
202        assert_eq!(
203            errors::Error::from(42_u32),
204            errors::Error::Other { source: 42 }
205        );
206    }
207
208    #[test]
209    fn concrete_generic_arguments_with_crate_path() {
210        assert_eq!(Value::<u32>::from(42), Value::Some(42));
211    }
212}