Macro sbor_derive::eager_replace

source ·
eager_replace!() { /* proc-macro */ }
Expand description

NOTE: This should probably be moved out of sbor to its own crate.

This macro is a powerful but simple general-purpose tool to ease building declarative macros which create new types.

§Motivation

Effectively it functions as a more powerful version of paste!, whilst bringing the power of quote!’s variable substitution to declarative macros.

This approach neatly solves the following cases:

  1. Wanting paste! to output strings or work with attributes other than doc.
  2. Avoiding defining internal macro_rules! to handle instances where you need to do a procedural macro repeat across two conflicting expansions per this stack overflow post.
  3. Improves readability of long procedural macros through substitution of repeated segments.

An example of case 1:

// Inside a macro_rules! expression:
eager_replace!{
    #[sbor(as_type = [!stringify! $my_inner_type])]
    $vis struct $my_type($my_inner_type)
}

§Specific functions

  • [!stringify! X Y " " Z] gives "X Y \" \" Z" - IMPORTANT: This uses token_stream.into_string() which is compiler-version dependent. Do not use if that is important. Instead, the output from concat should be independent of compiler version.
  • [!concat! X Y " " Z (Hello World)] gives "XY Z(HelloWorld)" by concatenating each argument without spaces, and recursing inside groups. String and char literals are first unquoted. Spaces can be added with “ “.
  • [!ident! X Y "Z"] gives an ident XYZ, using the same algorithm as concat.
  • [!literal! 31 u 32] gives 31u32, using the same algorithm as concat.
  • [!raw! abc #abc [!ident! test]] outputs its contents without any nested expansion, giving abc #abc [!ident! test].

Note that all functions except raw resolve in a nested manner as you would expected, e.g.

[!concat! X Y [!ident! Hello World] Z] // "XYHelloWorldZ"

§Variables for cleaner coding

You can define variables starting with # which can be used outside the set call. All of the following calls don’t return anything, but create a variable, which can be embedded later in the macro. See the Demonstration section for details

  • [!SET! #MyVar = ..] sets #MyVar to the given token stream.
  • [!SET:stringify! #MyVar = ..] sets #MyVar to the result of applying the stringify function to the token stream.
  • [!SET:concat! #MyVar = ..] sets #MyVar to the result of applying the concat function to the token stream.
  • [!SET:ident! #MyVar = ..] sets #MyVar to the result of applying the ident function to the token stream.
  • [!SET:literal! #MyVar = ..] sets #MyVar to the result of applying the literal function to the token stream.

§Demonstration

macro_rules! impl_marker_traits {
    {
        $(#[$attributes:meta])*
        $vis:vis $type_name_suffix:ident
        // Arbitrary generics
        $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? $( = $deflt:tt)? ),+ >)?
        [
            $($trait:ident),*
            $(,) // Optional trailing comma
        ]
    } => {eager_replace!{
        [!SET! #ImplGenerics = $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?]
        [!SET! #TypeGenerics = $(< $( $lt ),+ >)?]
        [!SET:ident! #MyType = Type $type_name_suffix #TypeGenerics]

        // Output for each marker trait
        $(
            impl #ImplGenerics $trait for #MyType
            {
                // Empty trait body
            }
        )*
    }}
}

§Future extensions

§String case conversion

Could in future support case conversion like paste.