Skip to main content

procedural_masquerade/
lib.rs

1//! # Custom `derive` pretending to be functional procedural macros on Rust 1.15
2//!
3//! This crate enables creating function-like macros (invoked as `foo!(...)`)
4//! with a procedural component,
5//! based on both custom `derive` (a.k.a. *Macros 1.1*) and `macro_rules!`.
6//!
7//! This convoluted mechanism enables such macros to run on stable Rust 1.15,
8//! even though functional procedural macros (a.k.a. *Macros 2.0*) are not available yet.
9//!
10//! A library defining such a macro needs two crates: a “normal” one, and a `proc-macro` one.
11//! In the example below we’ll call them `libfoo` and `libfoo-macros`, respectively.
12//!
13//! # Credits
14//!
15//! The trick that makes this crate work
16//! is based on an idea from [David Tolnay](https://github.com/dtolnay).
17//! Many thanks!
18//!
19//! # Example
20//!
21//! As a simple example, we’re going to re-implement the `stringify!` macro.
22//! This is useless since `stringify!` already exists in the standard library,
23//! and a bit absurd since this crate uses `stringify!` internally.
24//!
25//! Nevertheless, it serves as a simple example to demonstrate the use of this crate.
26//!
27//! ## The `proc-macro` crate
28//!
29//! The minimal `Cargo.toml` file is typical for Macros 1.1:
30//!
31//! ```toml
32//! [package]
33//! name = "libfoo-macros"
34//! version = "1.0.0"
35//!
36//! [lib]
37//! proc-macro = true
38//! ```
39//!
40//! In the code, we define the procedural part of our macro in a function.
41//! This function will not be used directly by end users,
42//! but it still needs to be re-exported to them
43//! (because of limitations in `macro_rules!`).
44//!
45//! To avoid name collisions, we include a long and explicit prefix in the function’s name.
46//!
47//! The function takes a string containing arbitrary Rust tokens,
48//! and returns a string that is parsed as *items*.
49//! The returned string can contain constants, statics, functions, `impl`s, etc.,
50//! but not expressions directly.
51//!
52//! ```rust
53//! #[macro_use] extern crate procedural_masquerade;
54//! extern crate proc_macro;
55//!
56//! define_proc_macros! {
57//!     #[allow(non_snake_case)]
58//!     pub fn foo_internal__stringify_const(input: &str) -> String {
59//!         format!("const STRINGIFIED: &'static str = {:?};", input)
60//!     }
61//! }
62//! ```
63//!
64//! A less trivial macro would probably use
65//! the [`syn`](https://github.com/dtolnay/syn/) crate to parse its input
66//! and the [`quote`](https://github.com/dtolnay/quote) crate to generate its output.
67//!
68//! ## The library crate
69//!
70//! ```toml
71//! [package]
72//! name = "libfoo"
73//! version = "1.0.0"
74//!
75//! [dependencies]
76//! libfoo-macros = {path = "./macros", version = "1.0"}
77//! ```
78//!
79//! ```rust
80//! #[macro_use] extern crate libfoo_macros;  // (1)
81//!
82//! pub use libfoo_macros::*;  // (2)
83//!
84//! define_invoke_proc_macro!(libfoo__invoke_proc_macro);  // (3)
85//!
86//! #[macro_export]
87//! macro_rules! foo_stringify {  // (4)
88//!     ( $( $tts: tt ) ) => {
89//!         {  // (5)
90//!             libfoo__invoke_proc_macro! {  // (6)
91//!                 foo_internal__stringify_const!( $( $tts ) )  // (7)
92//!             }
93//!             STRINGIFIED  // (8)
94//!         }
95//!     }
96//! }
97//! ```
98//!
99//! Let’s go trough the numbered lines one by one:
100//!
101//! 1. `libfoo` depends on `libfoo-macros`, and imports its macros.
102//! 2. Everything exported by `libfoo-macros` (which is one custom `derive`)
103//!    is re-exported to users of `libfoo`.
104//!    They’re not expected to use it directly,
105//!    but expansion of the `foo_stringify` macro needs it.
106//! 3. This macro invocation defines yet another macro, called `libfoo__invoke_proc_macro`,
107//!    which is also exported.
108//!    This indirection is necessary
109//!    because re-exporting `macro_rules!` macros doesn’t work currently,
110//!    and once again it is used by the expansion of `foo_stringify`.
111//!    Again, we use a long prefix to avoid name collisions.
112//! 4. Finally, we define the macro that we really want.
113//!    This one has a name that users will use.
114//! 5. The expansion of this macro will define some items,
115//!    whose names are not hygienic in `macro_rules`.
116//!    So we wrap everything in an extra `{…}` block to prevent these names for leaking.
117//! 6. Here we use the macro defined in (3),
118//!    which allows us to write something that look like invoking a functional procedural macro,
119//!    but really uses a custom `derive`.
120//!    This will define a type called `ProceduralMasqueradeDummyType`,
121//!    as a placeholder to use `derive`.
122//!    If `libfoo__invoke_proc_macro!` is to be used more than once,
123//!    each use needs to be nested in another block
124//!    so that the names of multiple dummy types don’t collide.
125//! 7. In addition to the dummy type,
126//!    the items returned by our procedural component are inserted here.
127//!    (In this case the `STRINGIFIED` constant.)
128//! 8. Finally, we write the expression that we want the macro to evaluate to.
129//!    This expression can use parts of `foo_stringify`’s input,
130//!    it can contain control-flow statements like `return` or `continue`,
131//!    and of course refer to procedurally-defined items.
132//!
133//! This macro can be used in an expression context.
134//! It expands to a block-expression that contains some items (as an implementation detail)
135//! and ends with another expression.
136//!
137//! ## For users
138//!
139//! Users of `libfoo` don’t need to worry about any of these implementation details.
140//! They can use the `foo_stringify` macro as if it were a simle `macro_rules` macro:
141//!
142//! ```rust
143//! #[macro_use] extern crate libfoo;
144//!
145//! fn main() {
146//!     do_something(foo_stringify!(1 + 2));
147//! }
148//!
149//! fn do_something(_: &str) { /* ... */ }
150//! ```
151//!
152//! # More
153//!
154//! To see a more complex example, look at
155//! [`cssparser`’s `src/macros.rs`](https://github.com/servo/rust-cssparser/blob/master/src/macros.rs)
156//! and
157//! [`cssparser-macros`’s `macros/lib.rs`](https://github.com/servo/rust-cssparser/blob/master/macros/lib.rs).
158
159/// This macro wraps `&str -> String` functions
160/// in custom `derive` implementations with `#[proc_macro_derive]`.
161///
162/// See crate documentation for details.
163#[macro_export]
164macro_rules! define_proc_macros {
165    (
166        $(
167            $( #[$attr:meta] )*
168            pub fn $proc_macro_name: ident ($input: ident : &str) -> String
169            $body: block
170        )+
171    ) => {
172        $(
173            $( #[$attr] )*
174            #[proc_macro_derive($proc_macro_name)]
175            pub fn $proc_macro_name(derive_input: ::proc_macro::TokenStream)
176                                    -> ::proc_macro::TokenStream {
177                fn wrapped($input: &str) -> String {
178                    $body
179                }
180
181                // syn uses a huge amount of stack in debug mode.
182                let derive_input_string = derive_input.to_string();
183                let handle =
184                    ::std::thread::Builder::new().stack_size(128 * 1024 * 1024).spawn(move || {
185                        wrapped($crate::_extract_input(&derive_input_string))
186                    }).unwrap();
187
188                handle.join().unwrap().parse().unwrap()
189            }
190        )+
191    }
192}
193
194/// Implementation detail of `define_proc_macros!`.
195///
196/// **This function is not part of the public API. It can change or be removed between any versions.**
197#[doc(hidden)]
198pub fn _extract_input(derive_input: &str) -> &str {
199    let mut input = derive_input;
200
201    for expected in &[
202        "#",
203        "[",
204        "allow",
205        "(",
206        "unused",
207        ")",
208        "]",
209        "enum",
210        "ProceduralMasqueradeDummyType",
211        "{",
212        "Input",
213        "=",
214        "(",
215        "0",
216        ",",
217        "stringify",
218        "!",
219        "(",
220    ] {
221        input = input.trim_start();
222        assert!(
223            input.starts_with(expected),
224            "expected prefix {:?} not found in {:?}",
225            expected,
226            derive_input
227        );
228        input = &input[expected.len()..];
229    }
230
231    for expected in [")", ")", ".", "0", ",", "}"].iter().rev() {
232        input = input.trim_end();
233        assert!(
234            input.ends_with(expected),
235            "expected suffix {:?} not found in {:?}",
236            expected,
237            derive_input
238        );
239        let end = input.len() - expected.len();
240        input = &input[..end];
241    }
242
243    input
244}
245
246/// This macro expands to the definition of another macro (whose name is given as a parameter).
247///
248/// See crate documentation for details.
249#[macro_export]
250macro_rules! define_invoke_proc_macro {
251    ($macro_name: ident) => {
252        /// Implementation detail of other macros in this crate.
253        #[doc(hidden)]
254        #[macro_export]
255        macro_rules! $macro_name {
256                                    ($proc_macro_name: ident ! $paren: tt) => {
257                                        #[derive($proc_macro_name)]
258                                        #[allow(unused)]
259                                        enum ProceduralMasqueradeDummyType {
260                                            // The magic happens here.
261                                            //
262                                            // We use an `enum` with an explicit discriminant
263                                            // because that is the only case where a type definition
264                                            // can contain a (const) expression.
265                                            //
266                                            // `(0, "foo").0` evalutes to 0, with the `"foo"` part ignored.
267                                            //
268                                            // By the time the `#[proc_macro_derive]` function
269                                            // implementing `#[derive($proc_macro_name)]` is called,
270                                            // `$paren` has already been replaced with the input of this inner macro,
271                                            // but `stringify!` has not been expanded yet.
272                                            //
273                                            // This how arbitrary tokens can be inserted
274                                            // in the input to the `#[proc_macro_derive]` function.
275                                            //
276                                            // Later, `stringify!(...)` is expanded into a string literal
277                                            // which is then ignored.
278                                            // Using `stringify!` enables passing arbitrary tokens
279                                            // rather than only what can be parsed as a const expression.
280                                            Input = (0, stringify! $paren ).0,
281                                        }
282                                    }
283                                }
284    };
285}