Need help writing a proc-macro parsing a function body

Hi there, I've tried to find some tutorial/blogs to learn on how to write a complex proc_macro but I'm not able to find one that goes as deep as I require.

I'd like to annotate a function like this:

struct bar;

#[MyAnnotation]
fn foo() {
  let special = bar::new();
  let c = special.handle();
}

The call in the function body special.handle() shall be found by the proc macro and introduce some specific code that reduces to write lot's of boilerplate when implementing the same feature by hand on the structure bar as this would be the same for an bar that would use this special handling.

What I get so far was writing a visitor that takes the AST provided in the pro_macro implementation to parse the function body that was annotated.

What I got so far:

#[proc_macro_attribute]
#[allow(non_snake_case)]
pub fn MyAnnotation(attr: TokenStream, item: TokenStream) -> TokenStream {
    // get the function this attribute is attached to
    let func = parse_macro_input!(item as ItemFn);
    let block = &func.block;
    // visit the function body to parse it
    let mut test = FunctionBody{};
    test.visit_block(block);
}

struct FunctionBody<'a> {}

impl<'a> Visit<'a> for FunctionBody<'a> {
    /// while visiting the statements of the thinkable function body we can prepare how this will be
    /// translated into the thinkable "state-machine"
    fn visit_stmt(&mut self, s: &'a Stmt) {
        match s {
            // an expression without a semicolon, this is usually one that will provide the result
            // of the function if it is the last expression
            Stmt::Expr(expr) => {
                println!("found expression");
            },
            // an expression with semicolon, this could be eg. a function/method call awaiting a thinkable
            Stmt::Semi(expr, token) => {
                println!("found semi-colon expression");
                match expr {
                    // an "foo.handle;" is represented as Field expression where the Member is "handle"
                    Expr::Field( ExprField {
                        dot_token: Dot, //Token![.],
                        member: Member::Named(
                            ident
                            /*proc_macro2::Ident {
                                inner: proc_macro2::imp::Ident::Compiler(i),
                                ..
                            }*/
                        ),
                        ..
                    }) => {
                        println!("found handle call? {:?}", ident);
                    },
                    Expr::Call(binding) => {

                    },
                    _ => (),
                }
            },
            _ => ()
        };
    }
}

The issue is, that I cannot find a way to match on the identifier name in this match axpression:

match expr {
                    // an "foo.handle;" is represented as Field expression where the Member is "handle"
                    Expr::Field( ExprField {
                        dot_token: Dot, //Token![.],
                        member: Member::Named(
                            ident
                            /*proc_macro2::Ident {
                                inner: proc_macro2::imp::Ident::Compiler(i),
                                ..
                            }*/
                        ),
                        ..
                    })

while printing the identifier to the console results in: found call? Ident { ident: "handle", span: #0 bytes(284..290) }, so the identifier is there. But the inner structure of the proc_macro2::Ident is private - therefore the compiler complains when pattern matching the inner values. Is there anyone here experience with this deep level of proc_macro development and could help out here?

Thx in advance.

You can extract the "value" of an Ident by converting it to a String with .to_string(), and if you "just" need to compare it against some known value (e.g., "handle" in your case), you can use == comparison with the ident being in the left hand side of the comparison:

match expr {
    | &Expr::Field(ExprField {
        member: Member::Named(ref ident),
        .. // not need to bind against fields you do not care about
    })
        if ident == "handle"
    => {
        // ...
    },

    | _ => {
        // ...
    },
}

Regarding XY problem:

  1. what about using Visit[Mut]::visit_expr_method_call()

    image

  2. Providing some magic method call to factor out some code logic sounds rather like the job of a (helper) trait; could you provide a "real case" example of what the replacement code would look like to see if that's the case?

Hey, that sound really "simple" and never thought of this kind of neat solution.

Well the aim is to use several "handle" calls that are part of the function to built up a state machine. The reason for this is that I'm not happy how the future (async/await) is not yet well supported for no_std environments and the workarounds that exists require ThreadLocalStatics to work, but in my bare-metal Raspberry Pi OS I'm running one "thread/process" per core and no further multi-threading. My adoption of the Futures pattern works really well without ever needing any "blocking thread". However, I'd like to reduce the code that manually need to be written to a most possible minimum and like to generate my cutom FutureStateMachine for an asynchronous executable function based on the parsed content body of this function....

I'll check if visit_expr_method_call might also be usefull and would reduce the complexity of my parsing function... Thx for this hint. May be I'm overcomplicating my problem and do struggle with a solution for this reason :blush:

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.