macro_compose/
lib.rs

1//! macro-compose is a library trying to simplify and organize proc-macros.
2//! It offers traits ([`Lint`], [`Expand`]) to split up the macro expansion into multiple smaller, reusable parts
3//! and structs the collect the results ([`Collector`], [`Context`]).
4//!
5//! # Example macro
6//! The following subsections show examples for different parts of this library.
7//!
8//! The examples are taken from the example macro in `examples/enum_from_str_macro` which implements a derive macro for `FromStr` for an enum.
9//! ## Linting and error handling
10//! The [`Lint`] trait is used to lint the macro input. [`Collector::error`] can be used to output errors.
11//! ### Example
12//! ```
13//! # extern crate proc_macro;
14//! use macro_compose::{Collector, Lint};
15//! use syn::{Data, DeriveInput, Error, Fields};
16//!
17//! struct EnsureEnumLint;
18//!
19//! impl Lint<DeriveInput> for EnsureEnumLint {
20//!     fn lint(&self, input: &DeriveInput, c: &mut Collector) {
21//!         match &input.data {
22//!             Data::Enum(e) => {
23//!                 for variant in e.variants.iter() {
24//!                     if variant.fields != Fields::Unit {
25//!                         c.error(Error::new_spanned(&variant.fields, "unexpected fields"))
26//!                     }
27//!                 }
28//!             }
29//!             _ => c.error(Error::new_spanned(input, "expected an enum")),
30//!         }
31//!     }
32//! }
33//! ```
34//! ## Expanding the macro
35//! The [`Expand`] trait is used to expand the macro.
36//!
37//! Once a `Lint` or `Expand` has reported an error to the collector, the macro will no longer be expanded.
38//! This way `Expand` implementations can assume that the data checked by `Lint`s is valid.
39//! Returning `None` from an `Expand` does **not** automatically report an error.
40//! ### Example
41//! ```
42//! # extern crate proc_macro;
43//! use macro_compose::{Collector, Expand};
44//! use proc_macro2::Ident;
45//! use syn::{parse_quote, Arm, Data, DeriveInput, Error, Fields, ItemImpl};
46//! # fn error_struct_ident(_: &DeriveInput) -> Ident {todo!()}
47//!
48//! struct ImplFromStrExpand;
49//!
50//! impl Expand<DeriveInput> for ImplFromStrExpand {
51//!     type Output = ItemImpl;
52//!
53//!     fn expand(&self, input: &DeriveInput, _: &mut Collector) -> Option<Self::Output> {
54//!         let variants = match &input.data {
55//!             Data::Enum(e) => &e.variants,
56//!             _ => unreachable!(),
57//!         };
58//!         let ident = &input.ident;
59//!
60//!         let arms = variants.iter().map(|v| -> Arm {
61//!             let v = &v.ident;
62//!             let name = v.to_string();
63//!             parse_quote!(
64//!                 #name => ::core::result::Result::Ok(#ident :: #v)
65//!             )
66//!         });
67//!
68//!         let ident = &input.ident;
69//!         let error = error_struct_ident(input);
70//!         Some(parse_quote!(
71//!             impl ::core::str::FromStr for #ident {
72//!                 type Err = #error;
73//!
74//!                 fn from_str(s: &::core::primitive::str) -> ::core::result::Result<Self, Self::Err> {
75//!                     match s {
76//!                         #(#arms,)*
77//!                         invalid => ::core::result::Result::Err( #error (::std::string::ToString::to_string(invalid))),
78//!                     }
79//!                 }
80//!             }
81//!         ))
82//!     }
83//! }
84//! ```
85//! ## Implementing the macro
86//! [`Context::new_parse`] can be used to create a context from a [`TokenStream`](proc_macro::TokenStream).
87//! This Context can be used to run `Lint`s and `Expand`s and get the resulting output.
88//! ### Example
89//! ```
90//! # extern crate proc_macro;
91//! use macro_compose::{Collector, Context};
92//! use proc_macro::TokenStream;
93//!
94//! # #[doc = "
95//! #[proc_macro_derive(FromStr)]
96//! pub fn derive_from_str(item: TokenStream) -> TokenStream {
97//!     let mut collector = Collector::new();
98//!
99//!     let mut ctx = Context::new_parse(&mut collector, item);
100//!     ctx.lint(&EnsureEnumLint);
101//!
102//!     ctx.expand(&ErrorStructExpand);
103//!     ctx.expand(&ImplDebugErrorStructExpand);
104//!     ctx.expand(&ImplFromStrExpand);
105//!
106//!     collector.finish()
107//! }
108//! # "]
109//! # struct Foo;
110//! ```
111
112#![deny(missing_docs, clippy::doc_markdown)]
113
114extern crate proc_macro;
115
116mod context;
117
118pub use context::{Collector, Context};
119
120use proc_macro2::TokenStream;
121use quote::ToTokens;
122
123/// Lint is used for linting the macro input
124///
125/// # Example
126/// ```
127/// use macro_compose::{Collector, Lint};
128/// use syn::{Data, DeriveInput, Error};
129///
130/// struct EnsureStructLint;
131///
132/// impl Lint<DeriveInput> for EnsureStructLint {
133///     fn lint(&self, input: &DeriveInput, c: &mut Collector) {
134///         if !matches!(&input.data, Data::Struct(_)) {
135///             c.error(Error::new_spanned(input, "expected a struct"));
136///         }
137///     }
138/// }
139/// ```
140pub trait Lint<I> {
141    /// lint the macro input
142    fn lint(&self, input: &I, c: &mut Collector);
143}
144
145/// Expand is used for expanding macros
146///
147/// # Example
148/// ```
149/// use macro_compose::{Collector, Expand};
150/// use syn::{parse_quote, DeriveInput, Error, ItemImpl};
151///
152/// struct ImplFooExpand;
153///
154/// impl Expand<DeriveInput> for ImplFooExpand {
155///     type Output = ItemImpl;
156///     
157///     fn expand(&self, input: &DeriveInput, c: &mut Collector) -> Option<Self::Output> {
158///         let ident = &input.ident;
159///         
160///         Some(parse_quote!(
161///             impl Foo for #ident {
162///                 fn bar(&self) {}
163///             }   
164///         ))
165///     }
166/// }
167/// ```
168pub trait Expand<I> {
169    /// the output generated by the expansion
170    type Output: ToTokens;
171
172    /// expand the macro
173    fn expand(&self, input: &I, c: &mut Collector) -> Option<Self::Output>;
174}
175
176/// a helper struct for expanding to nothing
177pub struct Nothing;
178
179impl ToTokens for Nothing {
180    fn to_tokens(&self, _: &mut TokenStream) {}
181}
182
183/// simply echo the input
184pub struct EchoExpand;
185
186impl<T: ToTokens + Clone> Expand<T> for EchoExpand {
187    type Output = T;
188
189    fn expand(&self, input: &T, _: &mut Collector) -> Option<Self::Output> {
190        Some(input.clone())
191    }
192}