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}