bijective_enum_map/
bijective.rs

1/// Map an enum into and from another type (or two types) using `From` in each direction.
2///
3/// The enum type must be specified, followed by the type to map the enum into (`$into`),
4/// optionally followed by the type to map into the enum (`$from`).
5/// If `$from` is not specified, it is set to `$into`.
6///
7/// The two types must be similar enough that the same value expression (e.g., a numeric or string
8/// literal) works for either; moreover, the expression must also be a valid pattern for a match
9/// arm, so arbitrarily complicated expressions are not permitted.
10/// In practice, the types should usually be the same, but specifying two different types could be
11/// used for converting into a static reference and trying to convert from a reference of
12/// unspecified lifetime, for example. (Such usage is more useful in `injective_enum_map`.)
13/// Unintended options like mapping into `Range<u8>` and from `u8` might be possible, but are
14/// not tested here. Note that non-unit variants are supported.
15///
16/// This map is intended to be "bijective", which means that it is both "surjective" and
17/// "injective". Being surjective means that every value of the target type to map the enum into
18/// should be associated with some enum variant. This is enforced by a `match` mapping values into
19/// enum variants; if your value expressions/patterns are fairly normal
20/// (no tricks with `|`, `..`, and the like), surjectivity will hold unless you repeat enum
21/// variants (causing some arms to be unreachable when mapping variants to values).
22///
23/// Being injective means that different enum variants should map into different
24/// values, so that they can be mapped back unambiguously.
25///
26/// If the map is not bijective, and either some enum variants are repeated or multiple enum
27/// variants map to the same value, then a compiler warning from `#[warn(unreachable_patterns)]`
28/// *should* be printed in most circumstances, but it could be a silent logic error. In such a
29/// case, only the first duplicate arm (in each direction) will be taken for the duplicated variant
30/// or value.
31///
32/// # Examples
33///
34/// ## Map into and from two other types:
35/// ```
36/// use bijective_enum_map::bijective_enum_map;
37/// #[derive(Debug, PartialEq, Eq)]
38/// enum AtMostTwo {
39///     Zero,
40///     One,
41///     Two,
42/// }
43///
44/// #[derive(Debug, PartialEq, Eq)]
45/// enum Other {
46///     Zeroeth,
47///     First,
48///     Second,
49/// }
50///
51/// bijective_enum_map! {
52///     AtMostTwo, Option<bool>,
53///     Zero <=> Some(false),
54///     One  <=> Some(true),
55///     Two  <=> None,
56/// }
57///
58/// // You can specify the same type twice for from/into, it has no effect.
59/// // Note that the path to other enums does have to be specified.
60/// bijective_enum_map! {
61///     AtMostTwo, Other, Other,
62///     Zero <=> Other::Zeroeth,
63///     One  <=> Other::First,
64///     Two  <=> Other::Second,
65/// }
66///
67/// // The compiler can infer that this is `Option::<bool>::from`
68/// assert_eq!(Option::from(AtMostTwo::One), Some(true));
69/// assert_eq!(AtMostTwo::from(None), AtMostTwo::Two);
70/// assert_eq!(AtMostTwo::from(Other::Zeroeth), AtMostTwo::Zero);
71/// ```
72///
73/// ## Map with a non-unit variant:
74/// ```
75/// use bijective_enum_map::bijective_enum_map;
76/// #[derive(Debug, PartialEq, Eq)]
77/// enum MaybeData {
78///     Data(String),
79///     Nothing,
80/// }
81///
82/// bijective_enum_map! {
83///     MaybeData, Option<String>,
84///     Data(data) <=> Some(data),
85///     Nothing    <=> None,
86/// }
87///
88/// assert_eq!(
89///     MaybeData::from(Some("What do you get when you multiply six by nine?".to_owned())),
90///     MaybeData::Data("What do you get when you multiply six by nine?".to_owned()),
91/// );
92/// assert_eq!(
93///     Option::from(MaybeData::Data("42".to_owned())),
94///     Some("42".to_owned()),
95/// );
96/// ```
97///
98/// ## Intentionally violate injectivity:
99/// ```
100/// use bijective_enum_map::bijective_enum_map;
101/// #[derive(Debug, PartialEq, Eq)]
102/// enum ParsedString {
103///     Quoted(String),
104///     Unquoted(String),
105///     Unknown(String),
106/// }
107///
108/// // Slightly extreme option to silence warnings; you should probably run your code without
109/// // a similar allow attribute in order to check that the warning which occurs is as expected.
110/// // It seems that `#[allow(unreachable_patterns)]` is overridden by
111/// // the `#[warn(unreachable_patterns)]` inside the macro.
112/// #[allow(warnings)]
113/// {
114///     bijective_enum_map! {
115///         ParsedString, String,
116///         // Because injectivity is violated, the order is significant.
117///         // With this order, all incoming strings are mapped to the `Unknown` variant.
118///         Unknown(string)  <=> string,
119///         Quoted(string)   <=> string,
120///         Unquoted(string) <=> string,
121///     }
122/// }
123///
124/// assert_eq!(
125///     String::from(ParsedString::Quoted("string".to_owned())),
126///     "string".to_owned(),
127/// );
128/// assert_eq!(
129///     String::from(ParsedString::Unquoted("string".to_owned())),
130///     "string".to_owned(),
131/// );
132/// assert_eq!(
133///     ParsedString::from("string".to_owned()),
134///     ParsedString::Unknown("string".to_owned()),
135/// );
136/// ```
137///
138/// ## Empty enum, mapped into and from another type:
139/// ```
140/// use bijective_enum_map::bijective_enum_map;
141/// #[derive(Debug, PartialEq, Eq)]
142/// enum Empty {}
143/// enum AnotherEmpty {}
144///
145/// // The trailing comma is always optional
146/// bijective_enum_map! { Empty, AnotherEmpty }
147///
148/// // The below is to confirm that the appropriate `From` implementations exist
149/// fn _new_empty() -> Empty {
150///     panic!()
151/// }
152/// fn _new_another_empty() -> AnotherEmpty {
153///     AnotherEmpty::from(_new_empty())
154/// }
155/// fn _round_trip() -> Empty {
156///     Empty::from(_new_another_empty())
157/// }
158/// ```
159#[macro_export]
160macro_rules! bijective_enum_map {
161    { $enum_ty:ty, $into:ty, $from:ty, $($body:tt)* } => {
162        $crate::__impl_from_enum! { $enum_ty, $into, $($body)* }
163        $crate::__impl_enum_from! { $enum_ty, $from, $($body)* }
164    };
165
166    { $enum_ty:ty, $into:ty, $from:ty } => {
167        $crate::__impl_from_enum! { $enum_ty, $into }
168        $crate::__impl_enum_from! { $enum_ty, $from }
169    };
170
171    { $enum_ty:ty, $both:ty, $($body:tt)* } => {
172        $crate::__impl_from_enum! { $enum_ty, $both, $($body)* }
173        $crate::__impl_enum_from! { $enum_ty, $both, $($body)* }
174    };
175
176    { $enum_ty:ty, $both:ty } => {
177        $crate::__impl_from_enum! { $enum_ty, $both }
178        $crate::__impl_enum_from! { $enum_ty, $both }
179    };
180}
181
182
183#[cfg(test)]
184mod tests {
185    use crate::bijective_enum_map;
186
187    #[test]
188    fn empty_both_specified() {
189        #[derive(Debug, PartialEq, Eq)]
190        enum Empty {}
191        enum AnotherEmpty {}
192
193        // The trailing comma is always optional
194        bijective_enum_map! { Empty, AnotherEmpty, AnotherEmpty }
195
196        // The below is to confirm that the appropriate `From` implementations exist
197        fn _new_empty() -> Empty {
198            panic!()
199        }
200        fn _new_another_empty() -> AnotherEmpty {
201            AnotherEmpty::from(_new_empty())
202        }
203        fn _round_trip() -> Empty {
204            Empty::from(_new_another_empty())
205        }
206    }
207
208    #[test]
209    fn empty_one_specified() {
210        #[derive(Debug, PartialEq, Eq)]
211        enum Empty {}
212        enum AnotherEmpty {}
213
214        // The trailing comma is always optional
215        bijective_enum_map! { Empty, AnotherEmpty, }
216
217        // The below is to confirm that the appropriate `From` implementations exist
218        fn _new_empty() -> Empty {
219            panic!()
220        }
221        fn _new_another_empty() -> AnotherEmpty {
222            AnotherEmpty::from(_new_empty())
223        }
224        fn _round_trip() -> Empty {
225            Empty::from(_new_another_empty())
226        }
227    }
228
229    #[test]
230    fn nonempty_both_specified() {
231        #[derive(Debug, PartialEq, Eq)]
232        enum Trivial {
233            Num(u8),
234        }
235
236        bijective_enum_map! {Trivial, u8, u8, Num(num) <=> num}
237
238        assert_eq!(Trivial::from(2_u8), Trivial::Num(2));
239        assert_eq!(u8::from(Trivial::Num(3)), 3);
240    }
241
242    #[test]
243    fn nonempty_one_specified() {
244        #[derive(Debug, PartialEq, Eq)]
245        enum Trivial {
246            Num(u8),
247        }
248
249        bijective_enum_map! {Trivial, u8, Num(num) <=> num}
250
251        assert_eq!(Trivial::from(2_u8), Trivial::Num(2));
252        assert_eq!(u8::from(Trivial::Num(3)), 3);
253    }
254
255    #[test]
256    fn nonempty_enums() {
257        #[derive(Debug, PartialEq, Eq)]
258        enum Enum {
259            One,
260            Two,
261            Three,
262        }
263
264        #[derive(Debug, PartialEq, Eq)]
265        enum Other {
266            Uno,
267            Dos,
268            Tres,
269        }
270
271        bijective_enum_map! {
272            Enum, Other, Other,
273            One   <=> Other::Uno,
274            Two   <=> Other::Dos,
275            Three <=> Other::Tres,
276        }
277
278        assert_eq!(Other::from(Enum::Three), Other::Tres);
279        assert_eq!(Enum::from(Other::Uno), Enum::One);
280    }
281
282    #[test]
283    fn trailing_commas() {
284        enum Empty {}
285        enum AnotherEmpty {}
286        enum YetAnotherEmpty {}
287        enum AFourthEmpty {}
288
289        enum Trivial {
290            Num(u8),
291        }
292        enum Trivial2 {
293            Num(u16),
294        }
295        enum Trivial3 {
296            Num(i8),
297        }
298        enum Trivial4 {
299            Num(i16),
300        }
301
302        bijective_enum_map!(Empty, AnotherEmpty, AnotherEmpty);
303        bijective_enum_map! { Empty, YetAnotherEmpty };
304        bijective_enum_map! {
305            AnotherEmpty, YetAnotherEmpty, YetAnotherEmpty,
306        };
307        bijective_enum_map! { Empty, AFourthEmpty, };
308
309        bijective_enum_map!(Trivial, u8, u8, Num(num) <=> num);
310        bijective_enum_map! { Trivial2, u16, Num(num) <=> num };
311        bijective_enum_map! {
312            Trivial3, i8, i8, Num(num) <=> num,
313        };
314        bijective_enum_map! { Trivial4, i16, Num(num) <=> num, };
315    }
316
317    #[test]
318    fn non_unit_variant() {
319        // These would be strings, but don't want to pull in `alloc`.
320        #[derive(Debug, PartialEq)]
321        enum Thing {
322            Player {
323                name:     &'static str,
324                hp:       u32,
325                strength: f32,
326            },
327            PhysicalObject {
328                name:     &'static str,
329                hp:       u32,
330            },
331            Spell {
332                name:     &'static str,
333                strength: f32,
334            },
335            Marker(&'static str),
336            HardcodedMarker(&'static str, u32),
337            Unknown(Stuff),
338        }
339
340        type Stuff = (u8, Option<&'static str>, Option<u32>, Option<f32>);
341
342        bijective_enum_map! {
343            Thing, Stuff,
344            Player { name, hp, strength } <=> (0, Some(name), Some(hp), Some(strength)),
345            PhysicalObject { name, hp }   <=> (1, Some(name), Some(hp), None),
346            Spell { name, strength }      <=> (2, Some(name), None, Some(strength)),
347            Marker(name)                  <=> (3, Some(name), None, None),
348            HardcodedMarker(name, id)     <=> (4, Some(name), Some(id), None),
349            Unknown(stuff)                <=> stuff,
350        }
351
352        assert_eq!(
353            Stuff::from(Thing::Player { name: "person", hp: 2, strength: 1.5 }),
354            (0, Some("person"), Some(2), Some(1.5)),
355        );
356        assert_eq!(
357            Stuff::from(Thing::Marker("place")),
358            (3, Some("place"), None, None),
359        );
360        assert_eq!(
361            Thing::from((1_u8, Some("object"), Some(100_u32), None)),
362            Thing::PhysicalObject { name: "object", hp: 100 },
363        );
364        assert_eq!(
365            Thing::from((1_u8, Some("object"), Some(100_u32), Some(1e30))),
366            Thing::Unknown((1_u8, Some("object"), Some(100_u32), Some(1e30))),
367        );
368    }
369
370    #[test]
371    fn intentionally_non_surjective() {
372        #[derive(Debug, PartialEq, Eq)]
373        enum Enum {
374            One,
375            Two,
376            Three,
377        }
378
379        #[derive(Debug, PartialEq, Eq)]
380        enum Other {
381            Uno,
382            Dos,
383            Tres,
384            Cuatro,
385        }
386
387        #[allow(warnings)]
388        {
389            bijective_enum_map! {
390                Enum, Other, Other,
391                One   <=> Other::Uno,
392                Two   <=> Other::Dos,
393                Three <=> Other::Tres,
394                Three <=> Other::Cuatro,
395            }
396        }
397
398        assert_eq!(Other::from(Enum::Three), Other::Tres);
399        assert_eq!(Enum::from(Other::Uno), Enum::One);
400        assert_eq!(Enum::from(Other::Cuatro), Enum::Three);
401    }
402
403    #[test]
404    fn intentionally_non_injective() {
405        #[derive(Debug, PartialEq, Eq)]
406        enum Enum {
407            One,
408            Two,
409            Three,
410        }
411
412        #[derive(Debug, PartialEq, Eq)]
413        enum Other {
414            Uno,
415            Dos,
416            Tres,
417            Cuatro,
418        }
419
420        #[allow(warnings)]
421        {
422            bijective_enum_map! {
423                Other, Enum,
424                Uno    <=> Enum::One,
425                Dos    <=> Enum::Two,
426                Tres   <=> Enum::Three,
427                Cuatro <=> Enum::Three,
428            }
429        }
430
431        assert_eq!(Other::from(Enum::Three), Other::Tres);
432        assert_eq!(Enum::from(Other::Uno), Enum::One);
433        assert_eq!(Enum::from(Other::Cuatro), Enum::Three);
434    }
435}
436
437#[cfg(doctest)]
438pub mod compile_fail_tests {
439    /// ```compile_fail,E0004
440    /// use bijective_enum_map::bijective_enum_map;
441    /// #[derive(Debug, PartialEq, Eq)]
442    /// enum Nonempty {
443    ///     Something,
444    /// }
445    ///
446    /// bijective_enum_map! {Nonempty, u8}
447    /// ```
448    pub fn _nonempty_but_nothing_provided() {}
449
450    /// ```compile_fail,E0004
451    /// use bijective_enum_map::bijective_enum_map;
452    /// #[derive(Debug, PartialEq, Eq)]
453    /// enum Nonempty {
454    ///     Something,
455    ///     SomethingElse,
456    /// }
457    ///
458    /// bijective_enum_map! { Nonempty, u8, Something <=> 0 }
459    /// ```
460    pub fn _nonempty_but_not_enough_provided() {}
461
462    /// ```compile_fail
463    /// #![deny(warnings)]
464    ///
465    /// use bijective_enum_map::bijective_enum_map;
466    /// #[derive(Debug, PartialEq, Eq)]
467    /// enum AtMostTwo {
468    ///     Zero,
469    ///     One,
470    ///     Two,
471    /// }
472    ///
473    /// bijective_enum_map! {
474    ///     AtMostTwo, bool,
475    ///     Zero <=> false,
476    ///     One  <=> true,
477    ///     Two  <=> false,
478    /// }
479    /// ```
480    pub fn _nonempty_not_injective_warning() {}
481
482    /// ```compile_fail
483    /// #![deny(warnings)]
484    ///
485    /// use bijective_enum_map::bijective_enum_map;
486    /// enum AtMostTwo {
487    ///     Zero,
488    ///     One,
489    ///     Two,
490    /// }
491    ///
492    /// enum BoolEnum {
493    ///     True,
494    ///     False,
495    /// }
496    ///
497    /// bijective_enum_map! {
498    ///     bool, AtMostTwo,
499    ///     False <=> AtMostTwo::Zero,
500    ///     True  <=> AtMostTwo::One,
501    ///     False <=> AtMostTwo::Two,
502    /// }
503    /// ```
504    pub fn _nonempty_not_surjective_warning() {}
505
506    /// ```compile_fail
507    /// #![deny(warnings)]
508    ///
509    /// use bijective_enum_map::bijective_enum_map;
510    /// #[derive(Debug, PartialEq, Eq)]
511    /// enum AtMostTwo {
512    ///     Zero,
513    ///     One,
514    ///     Two,
515    /// }
516    ///
517    /// enum Other {
518    ///     Uno,
519    ///     Dos,
520    /// }
521    ///
522    /// bijective_enum_map! {
523    ///     AtMostTwo, Other,
524    ///     Zero <=> Other::Uno,
525    ///     One  <=> Other::Uno,
526    ///     Two  <=> Other::Dos,
527    /// }
528    ///
529    /// let _ = AtMostTwo::from(Other::Uno);
530    /// ```
531    pub fn _nonempty_to_enum_not_injective_warning() {}
532
533    // Doesn't seem to have a compiler error number
534    /// ```compile_fail
535    /// use bijective_enum_map::bijective_enum_map;
536    /// enum Nonempty {
537    ///     Something,
538    /// }
539    ///
540    /// bijective_enum_map! {
541    ///     Nonempty, ()
542    ///     Something <=> ()
543    /// }
544    /// ```
545    pub fn _missing_comma() {}
546
547    /// ```
548    /// use bijective_enum_map::bijective_enum_map;
549    /// enum Nonempty {
550    ///     Something,
551    /// }
552    ///
553    /// bijective_enum_map! {
554    ///     Nonempty, (),
555    ///     Something <=> ()
556    /// }
557    /// ```
558    pub fn not_missing_comma() {}
559}