Search code examples
rustrust-proc-macros

Having trouble getting started with proc macros (why doesn't this work?)


I'm trying to follow this post to get my feet wet with proc macros (Can a procedural macro be debugged as a function?)

I came up with this to just to get a feel for how it all works:

extern crate proc_macro;
use proc_macro2::TokenStream;
use std::str::FromStr;
use syn;
use quote;

fn main() {
    let ts = TokenStream::from_str("fn foo() {}").unwrap();
    let _ts_str = ts.to_string();
    let parsed = syn::parse_macro_input!(ts);
}

But when I run in the Playground I get this error:

error[E0308]: mismatched types
   --> src/main.rs:10:18
    |
10  |     let parsed = syn::parse_macro_input!(ts);
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                  |
    |                  expected struct `proc_macro::TokenStream`, found struct `TokenStream2`
    |                  arguments to this function are incorrect
    |
note: function defined here
   --> /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.103/src/parse_macro_input.rs:138:8
    |
138 | pub fn parse<T: ParseMacroInput>(token_stream: TokenStream) -> Result<T> {
    |        ^^^^^
    = note: this error originates in the macro `$crate::parse_macro_input` which comes from the expansion of the macro `syn::parse_macro_input` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
  --> src/main.rs:10:18
   |
10 |     let parsed = syn::parse_macro_input!(ts);
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found struct `proc_macro::TokenStream`
   |
   = note: this error originates in the macro `$crate::parse_macro_input` which comes from the expansion of the macro `syn::parse_macro_input` (in Nightly builds, run with -Z macro-backtrace for more info)

I'm having trouble understanding what is going on, and could use some help. I've tried various typecasting but nothing seems to work.

I know as a Rust newbie I'm kinda jumping into the deep end of the pool here, but it is somewhat unavoidable. I'm running into little issues that I think will be barriers to getting Rust accepted for our application, and I'm trying to forge a path with as little friction as possible to convince our team (and myself) to move away from C/C++ in our embedded application. So I appreciate the help and patience here.


Solution

  • You have a proc_macro2::TokenStream while the macro is expecting a proc_macro::TokenStream. Fortunately, they can be converted between each other with the basic From/Into traits.


    You've not provided a type to parse into. You should have at least some idea of what kind of syntax you're expecting. Here you could use ItemFn or more generally Item. Though if you're making a #[derive()] macro, you'd use DeriveInput.


    From the documentation on parse_macro_input:

    This macro must be called from a function that returns proc_macro::TokenStream.

    This is because the macro has error handling built-in and will early-exit with a stream containing a compile_error! if the stream doesn't match the expected input type.


    You may run into issues with trying this in a independent binary like you have here since proc_macro can only be used within a procedural macro invocation. You can replace the macro with parse2 instead if that's what you want.

    Here's a complete working example:

    use proc_macro2::TokenStream;
    use std::str::FromStr;
    use syn::ItemFn;
    
    fn main() {
        let ts = TokenStream::from_str("fn foo() {}").unwrap();
        let parsed = syn::parse2::<ItemFn>(ts).unwrap();
        
        println!("{:?}", parsed);
    }
    

    See it working on the playground.