Skip to content

Commit eb5c5bf

Browse files
committed
Document procedural-masquarade
1 parent b1b6ed9 commit eb5c5bf

File tree

1 file changed

+198
-8
lines changed

1 file changed

+198
-8
lines changed

procedural-masquarade/lib.rs

Lines changed: 198 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,183 @@
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 and 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_masquarade;
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+
//! cssparser-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 the other `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 `ProceduralMasquaradeDummyType`,
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+
/////
160+
///// This indirection is necessary because re-exporting `macro_rules!` macros doesn’t work.
161+
///// Without it, when a library `libfoo` defines a macro whose expansion uses directly
162+
///// a macro
163+
164+
/// This macro wraps `&str -> String` functions
165+
/// in custom `derive` implementations with `#[proc_macro_derive]`.
166+
///
167+
/// See crate documentation for details.
1168
#[macro_export]
2169
macro_rules! define_proc_macros {
3170
(
4171
$(
5172
$( #[$attr:meta] )*
6-
pub fn $func:ident($input:ident: &str) -> String
7-
$body:block
173+
pub fn $proc_macro_name: ident ($input: ident : &str) -> String
174+
$body: block
8175
)+
9176
) => {
10177
$(
11178
$( #[$attr] )*
12-
#[proc_macro_derive($func)]
13-
pub fn $func(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
179+
#[proc_macro_derive($proc_macro_name)]
180+
pub fn $proc_macro_name(input: ::proc_macro::TokenStream) -> ::proc_macro::TokenStream {
14181
// Use another function to hide this one’s local variables from $block
15182
#[inline]
16183
fn implementation($input: &str) -> String $body
@@ -22,10 +189,10 @@ macro_rules! define_proc_macros {
22189
normalized.push(' ');
23190
}
24191

25-
let prefix = "#[allow(unused)] enum Dummy { Input = (0, stringify!(";
192+
let prefix = "#[allow(unused)] enum ProceduralMasqueradeDummyType { Input = (0, stringify!(";
26193
let suffix = ")).0, } ";
27-
assert!(normalized.starts_with(prefix), "Unexpected proc_macro_derive input {:?}", input);
28-
assert!(normalized.ends_with(suffix), "Unexpected proc_macro_derive input {:?}", input);
194+
assert!(normalized.starts_with(prefix), "expected prefix not found in {:?}", input);
195+
assert!(normalized.ends_with(suffix), "expected suffix not found in {:?}", input);
29196

30197
let start = prefix.len();
31198
let end = normalized.len() - suffix.len();
@@ -36,6 +203,9 @@ macro_rules! define_proc_macros {
36203
}
37204
}
38205

206+
/// This macro expands to the definition of another macro (whose name is given as a parameter).
207+
///
208+
/// See crate documentation for details.
39209
#[macro_export]
40210
macro_rules! define_invoke_proc_macro {
41211
($macro_name: ident) => {
@@ -44,7 +214,27 @@ macro_rules! define_invoke_proc_macro {
44214
($proc_macro_name: ident ! $paren: tt) => {
45215
#[derive($proc_macro_name)]
46216
#[allow(unused)]
47-
enum Dummy {
217+
enum ProceduralMasquaradeDummyType {
218+
// The magic happens here.
219+
//
220+
// We use an `enum` with an explicit discriminant
221+
// because that is the only case where a type definition
222+
// can contain a (const) expression.
223+
//
224+
// `(0, "foo").0` evalutes to 0, with the `"foo"` part ignored.
225+
//
226+
// By the time the `#[proc_macro_derive]` function
227+
// implementing `#[derive($proc_macro_name)]` is called,
228+
// `$paren` has already been replaced with the input of this inner macro,
229+
// but `stringify!` has not been expanded yet.
230+
//
231+
// This how arbitrary tokens can be inserted
232+
// in the input to the `#[proc_macro_derive]` function.
233+
//
234+
// Later, `stringify!(...)` is expanded into a string literal
235+
// which is then ignored.
236+
// Using `stringify!` enables passing arbitrary tokens
237+
// rather than only what can be parsed as a const expression.
48238
Input = (0, stringify! $paren ).0
49239
}
50240
}

0 commit comments

Comments
 (0)