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

#![deny(missing_docs, clippy::doc_markdown)]

extern crate proc_macro;

mod context;

pub use context::{Collector, Context};

use proc_macro2::TokenStream;
use quote::ToTokens;

/// Lint is used for linting the macro input
///
/// # Example
/// ```
/// use macro_compose::{Collector, Lint};
/// use syn::{Data, DeriveInput, Error};
///
/// struct EnsureStructLint;
///
/// impl Lint<DeriveInput> for EnsureStructLint {
///     fn lint(&self, input: &DeriveInput, c: &mut Collector) {
///         if !matches!(&input.data, Data::Struct(_)) {
///             c.error(Error::new_spanned(input, "expected a struct"));
///         }
///     }
/// }
/// ```
pub trait Lint<I> {
    /// lint the macro input
    fn lint(&self, input: &I, c: &mut Collector);
}

/// Expand is used for expanding macros
///
/// # Example
/// ```
/// use macro_compose::{Collector, Expand};
/// use syn::{parse_quote, DeriveInput, Error, ItemImpl};
///
/// struct ImplFooExpand;
///
/// impl Expand<DeriveInput> for ImplFooExpand {
///     type Output = ItemImpl;
///     
///     fn expand(&self, input: &DeriveInput, c: &mut Collector) -> Option<Self::Output> {
///         let ident = &input.ident;
///         
///         Some(parse_quote!(
///             impl Foo for #ident {
///                 fn bar(&self) {}
///             }   
///         ))
///     }
/// }
/// ```
pub trait Expand<I> {
    /// the output generated by the expansion
    type Output: ToTokens;

    /// expand the macro
    fn expand(&self, input: &I, c: &mut Collector) -> Option<Self::Output>;
}

/// a helper struct for expanding to nothing
pub struct Nothing;

impl ToTokens for Nothing {
    fn to_tokens(&self, _: &mut TokenStream) {}
}

/// simply echo the input
pub struct EchoExpand;

impl<T: ToTokens + Clone> Expand<T> for EchoExpand {
    type Output = T;

    fn expand(&self, input: &T, _: &mut Collector) -> Option<Self::Output> {
        Some(input.clone())
    }
}