impl_enum/
lib.rs

1//! Macros that make it more convenient to work with enums with variants that
2//! all implement the same trait(s).
3//!
4//! [macro@with_methods] allows you to easily delegate method calls to enum variants:
5//! ```
6#![doc = include_str!("../examples/with_methods.rs")]
7//! ```
8//!
9//! [macro@as_dyn] allows you to treat the enum as a trait object when necessary:
10//! ```
11#![doc = include_str!("../examples/as_dyn.rs")]
12//! ```
13
14#[cfg(feature = "as_dyn")]
15mod as_dyn;
16#[cfg(feature = "with_methods")]
17mod with_methods;
18
19use proc_macro::TokenStream;
20use syn::{spanned::Spanned, Error, Field, Fields, Variant};
21
22/// Generates methods for an enum that match on the enum
23/// and call given the method with the variant's first field.
24///
25/// Takes a list of whitespace separated function signatures as its arguments.
26///
27/// # Example
28/// ```
29#[doc = include_str!("../examples/with_methods.rs")]
30/// ```
31/// The macro generates an impl block equivalent to
32/// ```
33/// # use std::io::Write;
34/// # enum Writer { Cursor(std::fs::File), File { file: std::fs::File } }
35/// impl Writer {
36///     fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
37///         match self {
38///             Self::Cursor(first, ..) => first.write_all(buf),
39///             Self::File { file, .. } => file.write_all(buf),
40///         }
41///     }
42///     pub fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
43///         match self {
44///             Self::Cursor(first, ..) => first.write(buf),
45///             Self::File { file, .. } => file.write(buf),
46///         }
47///     }
48/// }
49/// ```
50#[cfg(feature = "with_methods")]
51#[proc_macro_attribute]
52pub fn with_methods(args: TokenStream, input: TokenStream) -> TokenStream {
53    with_methods::with_methods_impl(args, input)
54}
55
56/// Generates methods for an enum that match on the enum
57/// and return the variant's first field as a trait object.
58///
59/// Takes a comma-separated list of traits as an argument.
60/// The name of the trait is snake_cased for the method names.
61/// For example, for the trait `ExampleTrait`  it would generate
62/// ```
63/// # trait ExampleTrait {}
64/// # struct S;
65/// # impl S {
66/// fn as_dyn_example_trait(&self) -> &dyn ExampleTrait
67/// # { unimplemented!() }
68/// fn as_dyn_example_trait_mut(&mut self) -> &mut dyn ExampleTrait
69/// # { unimplemented!() }
70/// fn into_dyn_example_trait(self) -> Box<dyn ExampleTrait>
71/// # { unimplemented!() }
72/// # }
73/// ```
74///
75/// # Example
76/// ```
77#[doc = include_str!("../examples/as_dyn.rs")]
78/// ```
79/// The macro generates an impl block equivalent to
80/// ```
81/// # use std::io::Write;
82/// # enum Writer { Cursor(std::fs::File), File { file: std::fs::File } }
83/// impl Writer {
84///     fn as_dyn_write(&self) -> &dyn Write {
85///         match self {
86///             Self::Cursor(first, ..) => first as &dyn Write,
87///             Self::File { file, .. } => file as &dyn Write,
88///         }
89///     }
90///     fn as_dyn_write_mut(&mut self) -> &mut dyn Write {
91///         match self {
92///             Self::Cursor(first, ..) => first as &mut dyn Write,
93///             Self::File { file, .. } => file as &mut dyn Write,
94///         }
95///     }
96///     fn into_dyn_write(self) -> Box<dyn Write> {
97///         match self {
98///             Self::Cursor(first, ..) => Box::new(first) as Box<dyn Write>,
99///             Self::File { file, .. } => Box::new(file) as Box<dyn Write>,
100///         }
101///     }
102/// }
103/// ```
104#[cfg(feature = "as_dyn")]
105#[proc_macro_attribute]
106pub fn as_dyn(args: TokenStream, input: TokenStream) -> TokenStream {
107    as_dyn::as_dyn_impl(args, input)
108}
109
110fn first_field(variant: &Variant) -> syn::Result<&Field> {
111    match &variant.fields {
112        Fields::Named(fields) => fields.named.first(),
113        Fields::Unnamed(fields) => fields.unnamed.first(),
114        Fields::Unit => {
115            return Err(Error::new(
116                variant.span(),
117                "Unit variants are not supported",
118            ))
119        }
120    }
121    .ok_or_else(|| {
122        Error::new(
123            variant.fields.span(),
124            "Enum variants must have at least one field",
125        )
126    })
127}