Search code examples
rustrust-macros

Procedural macro inside Syntactic macro error


lets say I wan't to generate this with just syntactic macros:

struct Foo {
    bar: [Bar; 100],
    gee: [Gee; 100],
}

with just this macro call:

make_struct!(100; Bar, Gee);

The first and obvious problem is is that lowercase bar/gee which as far as I know is impossible to create on syntactic macros so I made a small utility proc macro.

use proc_macro::{Ident, Span, TokenTree, TokenStream};

/// Take identifier and return it lowercase
#[proc_macro]
pub fn lower(stream: TokenStream) -> TokenStream {
    let tt = stream.into_iter().nth(0).unwrap();
    if let TokenTree::Ident(ident) = tt {
        dbg!(&ident);
        let new_ident = ident.to_string().to_lowercase();
        dbg!(&new_ident);
        TokenTree::Ident(
            Ident::new(&new_ident[..], Span::mixed_site())).into()
    } else {
        panic!("Invalid input to `lower` macro, just accepts one literal");
    }
}

Then how do I use this macro inside a syntactic macro? Because this works:

macro_rules! test {
    ($x:ident) => {
        let lower!($x) = 10;
    }
}

test!(Foo); // -> let foo = 10;

But this fails:

macro_rules! test {
    ($x:ident) => {
        struct Test { lower!($x) : [$x; 100] }
    }
}

test!(Foo); // -> (); ERROR

Is this a compiler error? or I'm doing something wrong?


Solution

  • The problem you are encountering is that macro invocations are not allowed in arbitrary locations in the source code, but only certain contexts (like expressions and items).

    There is a workaround, however, and it is already implemented by the paste library which does what your proc macro does as part of its main job of concatenating identifiers. The macro can be invoked from a larger context and process the tokens within it however it likes. With paste:

    macro_rules! test {
        ($x:ident) => {
            paste::paste! {
                struct Test { [<$x:lower>] : [$x; 100] }
            }
        }
    }
    

    In use, the paste! macro by itself can be placed anywhere a macro invocation can, and it does nothing until it sees the token sequence [< ... >] telling it to process the tokens within those regions in particular. Thus paste bypasses the restriction on macro invocation positions.