Android 12.0.0 release 21
[automerger skipped] Mark ab/7061308 as merged in stage. am: 77a2f51816 -s ours am: a647bf253a -s ours

am skip reason: Change-Id I06497beddfc08af0a44c83a2b15f25790813fd74 with SHA-1 a99fbe9770 is in history

Original change: undetermined

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ib21870246b8fd87359103ecf3ed862f93ef010ba
tree: 63973525c2cdafb3066c97a9105e24d1f164dd6b
  1. .github/
  2. src/
  3. tests/
  4. .cargo_vcs_info.json
  5. .gitignore
  6. Android.bp
  7. build.rs
  8. Cargo.toml
  9. Cargo.toml.orig
  10. LICENSE-APACHE
  11. LICENSE-MIT
  12. METADATA
  13. MODULE_LICENSE_APACHE2
  14. OWNERS
  15. README.md
  16. TEST_MAPPING
README.md

Procedural macros in expression position

Since Rust 1.30, the language supports user-defined function-like procedural macros. However these can only be invoked in item position, not in statements or expressions.

This crate implements an alternative type of procedural macro that can be invoked in statement or expression position.

This approach works with any Rust version 1.31+.

Defining procedural macros

Two crates are required to define a procedural macro.

The implementation crate

This crate must contain nothing but procedural macros. Private helper functions and private modules are fine but nothing can be public.

» example of an implementation crate

Just like you would use a #[proc_macro] attribute to define a natively supported procedural macro, use proc-macro-hack's #[proc_macro_hack] attribute to define a procedural macro that works in expression position. The function signature is the same as for ordinary function-like procedural macros.

use proc_macro::TokenStream;
use proc_macro_hack::proc_macro_hack;
use quote::quote;
use syn::{parse_macro_input, Expr};

#[proc_macro_hack]
pub fn add_one(input: TokenStream) -> TokenStream {
    let expr = parse_macro_input!(input as Expr);
    TokenStream::from(quote! {
        1 + (#expr)
    })
}

The declaration crate

This crate is allowed to contain other public things if you need, for example traits or functions or ordinary macros.

» example of a declaration crate

Within the declaration crate there needs to be a re-export of your procedural macro from the implementation crate. The re-export also carries a #[proc_macro_hack] attribute.

use proc_macro_hack::proc_macro_hack;

/// Add one to an expression.
///
/// (Documentation goes here on the re-export, not in the other crate.)
#[proc_macro_hack]
pub use demo_hack_impl::add_one;

Both crates depend on proc-macro-hack:

[dependencies]
proc-macro-hack = "0.5"

Additionally, your implementation crate (but not your declaration crate) is a proc macro crate:

[lib]
proc-macro = true

Using procedural macros

Users of your crate depend on your declaration crate (not your implementation crate), then use your procedural macros as usual.

» example of a downstream crate

use demo_hack::add_one;

fn main() {
    let two = 2;
    let nine = add_one!(two) + add_one!(2 + 3);
    println!("nine = {}", nine);
}

Limitations

  • Only proc macros in expression position are supported. Proc macros in pattern position (#20) are not supported.

  • By default, nested invocations are not supported i.e. the code emitted by a proc-macro-hack macro invocation cannot contain recursive calls to the same proc-macro-hack macro nor calls to any other proc-macro-hack macros. Use proc-macro-nested if you require support for nested invocations.

  • By default, hygiene is structured such that the expanded code can‘t refer to local variables other than those passed by name somewhere in the macro input. If your macro must refer to local variables that don’t get named in the macro input, use #[proc_macro_hack(fake_call_site)] on the re-export in your declaration crate. Most macros won't need this.

  • On compilers that are new enough to natively support proc macros in expression position, proc-macro-hack does not automatically use that support, since the hygiene can be subtly different between the two implementations. To opt in to compiling your macro to native #[proc_macro] on sufficiently new compilers, use #[proc_macro_hack(only_hack_old_rustc)] on the re-export in your declaration crate.

License