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}