blob: e9b80e69ab80c45dc8098c1b305046a92359c363 [file] [log] [blame]
Jason Macnake62a2eb2020-03-19 20:41:17 +00001//! As of Rust 1.30, the language supports user-defined function-like procedural
2//! macros. However these can only be invoked in item position, not in
3//! statements or expressions.
4//!
5//! This crate implements an alternative type of procedural macro that can be
6//! invoked in statement or expression position.
7//!
8//! # Defining procedural macros
9//!
10//! Two crates are required to define a procedural macro.
11//!
12//! ## The implementation crate
13//!
14//! This crate must contain nothing but procedural macros. Private helper
15//! functions and private modules are fine but nothing can be public.
16//!
17//! [> example of an implementation crate][demo-hack-impl]
18//!
19//! Just like you would use a #\[proc_macro\] attribute to define a natively
20//! supported procedural macro, use proc-macro-hack's #\[proc_macro_hack\]
21//! attribute to define a procedural macro that works in expression position.
22//! The function signature is the same as for ordinary function-like procedural
23//! macros.
24//!
25//! ```
26//! extern crate proc_macro;
27//!
28//! use proc_macro::TokenStream;
29//! use proc_macro_hack::proc_macro_hack;
30//! use quote::quote;
31//! use syn::{parse_macro_input, Expr};
32//!
33//! # const IGNORE: &str = stringify! {
34//! #[proc_macro_hack]
35//! # };
36//! pub fn add_one(input: TokenStream) -> TokenStream {
37//! let expr = parse_macro_input!(input as Expr);
38//! TokenStream::from(quote! {
39//! 1 + (#expr)
40//! })
41//! }
42//! #
43//! # fn main() {}
44//! ```
45//!
46//! ## The declaration crate
47//!
48//! This crate is allowed to contain other public things if you need, for
49//! example traits or functions or ordinary macros.
50//!
51//! [> example of a declaration crate][demo-hack]
52//!
53//! Within the declaration crate there needs to be a re-export of your
54//! procedural macro from the implementation crate. The re-export also carries a
55//! \#\[proc_macro_hack\] attribute.
56//!
57//! ```
58//! use proc_macro_hack::proc_macro_hack;
59//!
60//! /// Add one to an expression.
61//! ///
62//! /// (Documentation goes here on the re-export, not in the other crate.)
63//! #[proc_macro_hack]
64//! pub use demo_hack_impl::add_one;
65//! #
66//! # fn main() {}
67//! ```
68//!
69//! Both crates depend on `proc-macro-hack`:
70//!
71//! ```toml
72//! [dependencies]
73//! proc-macro-hack = "0.5"
74//! ```
75//!
76//! Additionally, your implementation crate (but not your declaration crate) is
77//! a proc macro crate:
78//!
79//! ```toml
80//! [lib]
81//! proc-macro = true
82//! ```
83//!
84//! # Using procedural macros
85//!
86//! Users of your crate depend on your declaration crate (not your
87//! implementation crate), then use your procedural macros as usual.
88//!
89//! [> example of a downstream crate][example]
90//!
91//! ```
92//! use demo_hack::add_one;
93//!
94//! fn main() {
95//! let two = 2;
96//! let nine = add_one!(two) + add_one!(2 + 3);
97//! println!("nine = {}", nine);
98//! }
99//! ```
100//!
101//! [demo-hack-impl]: https://github.com/dtolnay/proc-macro-hack/tree/master/demo-hack-impl
102//! [demo-hack]: https://github.com/dtolnay/proc-macro-hack/tree/master/demo-hack
103//! [example]: https://github.com/dtolnay/proc-macro-hack/tree/master/example
104//!
105//! # Limitations
106//!
107//! - Only proc macros in expression position are supported. Proc macros in
108//! pattern position ([#20]) are not supported.
109//!
110//! - By default, nested invocations are not supported i.e. the code emitted by
111//! a proc-macro-hack macro invocation cannot contain recursive calls to the
112//! same proc-macro-hack macro nor calls to any other proc-macro-hack macros.
113//! Use [`proc-macro-nested`] if you require support for nested invocations.
114//!
115//! - By default, hygiene is structured such that the expanded code can't refer
116//! to local variables other than those passed by name somewhere in the macro
117//! input. If your macro must refer to *local* variables that don't get named
118//! in the macro input, use `#[proc_macro_hack(fake_call_site)]` on the
119//! re-export in your declaration crate. *Most macros won't need this.*
120//!
121//! [#10]: https://github.com/dtolnay/proc-macro-hack/issues/10
122//! [#20]: https://github.com/dtolnay/proc-macro-hack/issues/20
123//! [`proc-macro-nested`]: https://docs.rs/proc-macro-nested
124
125#![recursion_limit = "512"]
126#![cfg_attr(feature = "cargo-clippy", allow(renamed_and_removed_lints))]
127#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
128
129extern crate proc_macro;
130
131use proc_macro2::{Span, TokenStream, TokenTree};
132use quote::{format_ident, quote, ToTokens};
133use std::fmt::Write;
134use syn::ext::IdentExt;
135use syn::parse::{Parse, ParseStream, Result};
136use syn::{braced, bracketed, parenthesized, parse_macro_input, token, Ident, LitInt, Token};
137
138type Visibility = Option<Token![pub]>;
139
140enum Input {
141 Export(Export),
142 Define(Define),
143}
144
145// pub use demo_hack_impl::{m1, m2 as qrst};
146struct Export {
147 attrs: TokenStream,
148 vis: Visibility,
149 from: Ident,
150 macros: Vec<Macro>,
151}
152
153// pub fn m1(input: TokenStream) -> TokenStream { ... }
154struct Define {
155 attrs: TokenStream,
156 name: Ident,
157 body: TokenStream,
158}
159
160struct Macro {
161 name: Ident,
162 export_as: Ident,
163}
164
165impl Parse for Input {
166 fn parse(input: ParseStream) -> Result<Self> {
167 let ahead = input.fork();
168 parse_attributes(&ahead)?;
169 ahead.parse::<Visibility>()?;
170
171 if ahead.peek(Token![use]) {
172 input.parse().map(Input::Export)
173 } else if ahead.peek(Token![fn]) {
174 input.parse().map(Input::Define)
175 } else {
176 Err(input.error("unexpected input to #[proc_macro_hack]"))
177 }
178 }
179}
180
181impl Parse for Export {
182 fn parse(input: ParseStream) -> Result<Self> {
183 let attrs = input.call(parse_attributes)?;
184 let vis: Visibility = input.parse()?;
185 input.parse::<Token![use]>()?;
186 input.parse::<Option<Token![::]>>()?;
187 let from: Ident = input.parse()?;
188 input.parse::<Token![::]>()?;
189
190 let mut macros = Vec::new();
191 if input.peek(token::Brace) {
192 let content;
193 braced!(content in input);
194 loop {
195 macros.push(content.parse()?);
196 if content.is_empty() {
197 break;
198 }
199 content.parse::<Token![,]>()?;
200 if content.is_empty() {
201 break;
202 }
203 }
204 } else {
205 macros.push(input.parse()?);
206 }
207
208 input.parse::<Token![;]>()?;
209 Ok(Export {
210 attrs,
211 vis,
212 from,
213 macros,
214 })
215 }
216}
217
218impl Parse for Define {
219 fn parse(input: ParseStream) -> Result<Self> {
220 let attrs = input.call(parse_attributes)?;
221 let vis: Visibility = input.parse()?;
222 if vis.is_none() {
223 return Err(input.error("functions tagged with `#[proc_macro_hack]` must be `pub`"));
224 }
225
226 input.parse::<Token![fn]>()?;
227 let name: Ident = input.parse()?;
228 let body: TokenStream = input.parse()?;
229 Ok(Define { attrs, name, body })
230 }
231}
232
233impl Parse for Macro {
234 fn parse(input: ParseStream) -> Result<Self> {
235 let name: Ident = input.parse()?;
236 let renamed: Option<Token![as]> = input.parse()?;
237 let export_as = if renamed.is_some() {
238 input.parse()?
239 } else {
240 name.clone()
241 };
242 Ok(Macro { name, export_as })
243 }
244}
245
246fn parse_attributes(input: ParseStream) -> Result<TokenStream> {
247 let mut attrs = TokenStream::new();
248 while input.peek(Token![#]) {
249 let pound: Token![#] = input.parse()?;
250 pound.to_tokens(&mut attrs);
251 let content;
252 let bracket_token = bracketed!(content in input);
253 let content: TokenStream = content.parse()?;
254 bracket_token.surround(&mut attrs, |tokens| content.to_tokens(tokens));
255 }
256 Ok(attrs)
257}
258
259#[proc_macro_attribute]
260pub fn proc_macro_hack(
261 args: proc_macro::TokenStream,
262 input: proc_macro::TokenStream,
263) -> proc_macro::TokenStream {
264 proc_macro::TokenStream::from(match parse_macro_input!(input) {
265 Input::Export(export) => {
266 let args = parse_macro_input!(args as ExportArgs);
267 expand_export(export, args)
268 }
269 Input::Define(define) => {
270 parse_macro_input!(args as DefineArgs);
271 expand_define(define)
272 }
273 })
274}
275
276mod kw {
277 syn::custom_keyword!(derive);
278 syn::custom_keyword!(fake_call_site);
279 syn::custom_keyword!(internal_macro_calls);
280 syn::custom_keyword!(support_nested);
281}
282
283struct ExportArgs {
284 support_nested: bool,
285 internal_macro_calls: u16,
286 fake_call_site: bool,
287}
288
289impl Parse for ExportArgs {
290 fn parse(input: ParseStream) -> Result<Self> {
291 let mut args = ExportArgs {
292 support_nested: false,
293 internal_macro_calls: 0,
294 fake_call_site: false,
295 };
296
297 while !input.is_empty() {
298 let ahead = input.lookahead1();
299 if ahead.peek(kw::support_nested) {
300 input.parse::<kw::support_nested>()?;
301 args.support_nested = true;
302 } else if ahead.peek(kw::internal_macro_calls) {
303 input.parse::<kw::internal_macro_calls>()?;
304 input.parse::<Token![=]>()?;
305 let calls = input.parse::<LitInt>()?.base10_parse()?;
306 args.internal_macro_calls = calls;
307 } else if ahead.peek(kw::fake_call_site) {
308 input.parse::<kw::fake_call_site>()?;
309 args.fake_call_site = true;
310 } else {
311 return Err(ahead.error());
312 }
313 if input.is_empty() {
314 break;
315 }
316 input.parse::<Token![,]>()?;
317 }
318
319 Ok(args)
320 }
321}
322
323struct DefineArgs;
324
325impl Parse for DefineArgs {
326 fn parse(_input: ParseStream) -> Result<Self> {
327 Ok(DefineArgs)
328 }
329}
330
331struct EnumHack {
332 token_stream: TokenStream,
333}
334
335impl Parse for EnumHack {
336 fn parse(input: ParseStream) -> Result<Self> {
337 input.parse::<Token![enum]>()?;
338 input.parse::<Ident>()?;
339
340 let braces;
341 braced!(braces in input);
342 braces.parse::<Ident>()?;
343 braces.parse::<Token![=]>()?;
344
345 let parens;
346 parenthesized!(parens in braces);
347 parens.parse::<Ident>()?;
348 parens.parse::<Token![!]>()?;
349
350 let inner;
351 braced!(inner in parens);
352 let token_stream: TokenStream = inner.parse()?;
353
354 parens.parse::<Token![,]>()?;
355 parens.parse::<TokenTree>()?;
356 braces.parse::<Token![.]>()?;
357 braces.parse::<TokenTree>()?;
358 braces.parse::<Token![,]>()?;
359
360 Ok(EnumHack { token_stream })
361 }
362}
363
364#[doc(hidden)]
365#[proc_macro_derive(ProcMacroHack)]
366pub fn enum_hack(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
367 let inner = parse_macro_input!(input as EnumHack);
368 proc_macro::TokenStream::from(inner.token_stream)
369}
370
371struct FakeCallSite {
372 derive: Ident,
373 rest: TokenStream,
374}
375
376impl Parse for FakeCallSite {
377 fn parse(input: ParseStream) -> Result<Self> {
378 input.parse::<Token![#]>()?;
379 let attr;
380 bracketed!(attr in input);
381 attr.parse::<kw::derive>()?;
382 let path;
383 parenthesized!(path in attr);
384 Ok(FakeCallSite {
385 derive: path.parse()?,
386 rest: input.parse()?,
387 })
388 }
389}
390
391#[doc(hidden)]
392#[proc_macro_attribute]
393pub fn fake_call_site(
394 args: proc_macro::TokenStream,
395 input: proc_macro::TokenStream,
396) -> proc_macro::TokenStream {
397 let args = TokenStream::from(args);
398 let span = match args.into_iter().next() {
399 Some(token) => token.span(),
400 None => return input,
401 };
402
403 let input = parse_macro_input!(input as FakeCallSite);
404 let mut derive = input.derive;
405 derive.set_span(span);
406 let rest = input.rest;
407
408 let expanded = quote! {
409 #[derive(#derive)]
410 #rest
411 };
412
413 proc_macro::TokenStream::from(expanded)
414}
415
416fn expand_export(export: Export, args: ExportArgs) -> TokenStream {
417 let dummy = dummy_name_for_export(&export);
418
419 let attrs = export.attrs;
420 let vis = export.vis;
421 let macro_export = match vis {
422 Some(_) => quote!(#[macro_export]),
423 None => quote!(),
424 };
425 let crate_prefix = vis.map(|_| quote!($crate::));
426 let enum_variant = if args.support_nested {
427 if args.internal_macro_calls == 0 {
428 quote!(Nested)
429 } else {
430 format_ident!("Nested{}", args.internal_macro_calls).to_token_stream()
431 }
432 } else {
433 quote!(Value)
434 };
435
436 let from = export.from;
437 let rules = export
438 .macros
439 .into_iter()
440 .map(|Macro { name, export_as }| {
441 let actual_name = actual_proc_macro_name(&name);
442 let dispatch = dispatch_macro_name(&name);
443 let call_site = call_site_macro_name(&name);
444
445 let export_dispatch = if args.support_nested {
446 quote! {
447 #[doc(hidden)]
448 #vis use proc_macro_nested::dispatch as #dispatch;
449 }
450 } else {
451 quote!()
452 };
453
454 let proc_macro_call = if args.support_nested {
455 let extra_bangs = (0..args.internal_macro_calls).map(|_| quote!(!));
456 quote! {
457 #crate_prefix #dispatch! { ($($proc_macro)*) #(#extra_bangs)* }
458 }
459 } else {
460 quote! {
461 proc_macro_call!()
462 }
463 };
464
465 let export_call_site = if args.fake_call_site {
466 quote! {
467 #[doc(hidden)]
468 #vis use proc_macro_hack::fake_call_site as #call_site;
469 }
470 } else {
471 quote!()
472 };
473
474 let do_derive = if !args.fake_call_site {
475 quote! {
476 #[derive(#crate_prefix #actual_name)]
477 }
478 } else if crate_prefix.is_some() {
479 quote! {
480 use #crate_prefix #actual_name;
481 #[#crate_prefix #call_site ($($proc_macro)*)]
482 #[derive(#actual_name)]
483 }
484 } else {
485 quote! {
486 #[#call_site ($($proc_macro)*)]
487 #[derive(#actual_name)]
488 }
489 };
490
491 quote! {
492 #[doc(hidden)]
493 #vis use #from::#actual_name;
494
495 #export_dispatch
496 #export_call_site
497
498 #attrs
499 #macro_export
500 macro_rules! #export_as {
501 ($($proc_macro:tt)*) => {{
502 #do_derive
503 enum ProcMacroHack {
504 #enum_variant = (stringify! { $($proc_macro)* }, 0).1,
505 }
506 #proc_macro_call
507 }};
508 }
509 }
510 })
511 .collect();
512
513 wrap_in_enum_hack(dummy, rules)
514}
515
516fn expand_define(define: Define) -> TokenStream {
517 let attrs = define.attrs;
518 let name = define.name;
519 let dummy = actual_proc_macro_name(&name);
520 let body = define.body;
521
522 quote! {
523 mod #dummy {
524 extern crate proc_macro;
525 pub use self::proc_macro::*;
526 }
527
528 #attrs
529 #[proc_macro_derive(#dummy)]
530 pub fn #dummy(input: #dummy::TokenStream) -> #dummy::TokenStream {
531 use std::iter::FromIterator;
532
533 let mut iter = input.into_iter();
534 iter.next().unwrap(); // `enum`
535 iter.next().unwrap(); // `ProcMacroHack`
536
537 let mut braces = match iter.next().unwrap() {
538 #dummy::TokenTree::Group(group) => group.stream().into_iter(),
539 _ => unimplemented!(),
540 };
541 let variant = braces.next().unwrap(); // `Value` or `Nested`
542 let varname = variant.to_string();
543 let support_nested = varname.starts_with("Nested");
544 braces.next().unwrap(); // `=`
545
546 let mut parens = match braces.next().unwrap() {
547 #dummy::TokenTree::Group(group) => group.stream().into_iter(),
548 _ => unimplemented!(),
549 };
550 parens.next().unwrap(); // `stringify`
551 parens.next().unwrap(); // `!`
552
553 let inner = match parens.next().unwrap() {
554 #dummy::TokenTree::Group(group) => group.stream(),
555 _ => unimplemented!(),
556 };
557
558 let output: #dummy::TokenStream = #name(inner.clone());
559
560 fn count_bangs(input: #dummy::TokenStream) -> usize {
561 let mut count = 0;
562 for token in input {
563 match token {
564 #dummy::TokenTree::Punct(punct) => {
565 if punct.as_char() == '!' {
566 count += 1;
567 }
568 }
569 #dummy::TokenTree::Group(group) => {
570 count += count_bangs(group.stream());
571 }
572 _ => {}
573 }
574 }
575 count
576 }
577
578 // macro_rules! proc_macro_call {
579 // () => { #output }
580 // }
581 #dummy::TokenStream::from_iter(vec![
582 #dummy::TokenTree::Ident(
583 #dummy::Ident::new("macro_rules", #dummy::Span::call_site()),
584 ),
585 #dummy::TokenTree::Punct(
586 #dummy::Punct::new('!', #dummy::Spacing::Alone),
587 ),
588 #dummy::TokenTree::Ident(
589 #dummy::Ident::new(
590 &if support_nested {
591 let extra_bangs = if varname == "Nested" {
592 0
593 } else {
594 varname["Nested".len()..].parse().unwrap()
595 };
596 format!("proc_macro_call_{}", extra_bangs + count_bangs(inner))
597 } else {
598 String::from("proc_macro_call")
599 },
600 #dummy::Span::call_site(),
601 ),
602 ),
603 #dummy::TokenTree::Group(
604 #dummy::Group::new(#dummy::Delimiter::Brace, #dummy::TokenStream::from_iter(vec![
605 #dummy::TokenTree::Group(
606 #dummy::Group::new(#dummy::Delimiter::Parenthesis, #dummy::TokenStream::new()),
607 ),
608 #dummy::TokenTree::Punct(
609 #dummy::Punct::new('=', #dummy::Spacing::Joint),
610 ),
611 #dummy::TokenTree::Punct(
612 #dummy::Punct::new('>', #dummy::Spacing::Alone),
613 ),
614 #dummy::TokenTree::Group(
615 #dummy::Group::new(#dummy::Delimiter::Brace, output),
616 ),
617 ])),
618 ),
619 ])
620 }
621
622 fn #name #body
623 }
624}
625
626fn actual_proc_macro_name(conceptual: &Ident) -> Ident {
627 format_ident!("proc_macro_hack_{}", conceptual)
628}
629
630fn dispatch_macro_name(conceptual: &Ident) -> Ident {
631 format_ident!("proc_macro_call_{}", conceptual)
632}
633
634fn call_site_macro_name(conceptual: &Ident) -> Ident {
635 format_ident!("proc_macro_fake_call_site_{}", conceptual)
636}
637
638fn dummy_name_for_export(export: &Export) -> String {
639 let mut dummy = String::new();
640 let from = export.from.unraw().to_string();
641 write!(dummy, "_{}{}", from.len(), from).unwrap();
642 for m in &export.macros {
643 let name = m.name.unraw().to_string();
644 write!(dummy, "_{}{}", name.len(), name).unwrap();
645 }
646 dummy
647}
648
649fn wrap_in_enum_hack(dummy: String, inner: TokenStream) -> TokenStream {
650 let dummy = Ident::new(&dummy, Span::call_site());
651 quote! {
652 #[derive(proc_macro_hack::ProcMacroHack)]
653 enum #dummy {
654 Value = (stringify! { #inner }, 0).1,
655 }
656 }
657}