Proc-macro modify tokens of inner macro


#1

As part of some convoluted compile-time magic I’m tinkering with, I’ve come across an issue I can’t seem to solve myself. Basically, I’m trying to parse the arguments/contents of a macro (eg: println!) as a valid Rust expression, in order to inspect it and also optionally apply some transformation (such as dereffing all field accesses). However, I’ve come across an issue where that appears to stem from macro hygeine/scoping etc… I suspect this because the cargo expand output looks fine, but an actual compile fails with failed to resolve. Could not findstdin{{root}}`.

In order to make this easier to digest, I’ve wrapped it up into a small testcase. I’d really appreciate it if somebody could chuck it a look and explain how I could resolve it.

Also, this was the only way I got the TokenTree -> Expr -> TokenTree transformation to work in the first place, and its really ugly. I hope there’s a more idiomatic way to do this?

        ExprKind::Mac(ref mut mac) => {
            /// First of all, these next 20 lines seem extremely convoluted, but they are
            /// the only way I could work out (from the `proc_macro`, `syn`, and `proc_macro2`
            /// public apis) how to convert the `Vec<TokenTree>` of a `Mac` into an `Expr`
            /// and back again.
            let stream: proc_macro::TokenStream = {
                let tts = &mac.tokens;
                let tokens = quote! { #(#tts)* };
                tokens.into()
            };
            let maybe_expr = parse::<Expr>(stream.clone());
            
            let new_tokens_stream: proc_macro::TokenStream = if let Ok(expr) = maybe_expr {
                println!("Successfully parsed macro contents as expr: {:?}", expr);
                let new_expr = (BasicFolder).fold_expr(expr);
                let new_tokens = quote! { #new_expr };
                println!("Output: {}", new_tokens);
                new_tokens.into()
            } else {
                stream
            };
            let new_token_stream: proc_macro2::TokenStream = new_tokens_stream.into();

            let tts: Vec<TokenTree> = new_token_stream.into_iter().map(|tt| TokenTree(tt)).collect();

            mac.tokens = tts;
        }

(In case anyone’s wondering: I understand I could special-case this by providing custom Display and Debug impls for my-secret-container-type, instead of manually inserting derefs, however, I need to insert derefs in other places anyway so it would be so elegant if I could do it inside macros too.)