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