3

I am experimenting with Rust procedural macros.

I would like to be able to create a macro that would be used to generate JNI invocation boilerplate. Something like

jni_method!{com.purplefrog.rust_callable.Widget, fn echo_str(&str)->String}

I have the following code so far (playground):

#[macro_use]
extern crate syn; // 1.0.33

use syn::parse::{Parse, ParseStream};
use syn::Signature;

struct Arguments {
    name: proc_macro2::Ident,
    signature: Signature,
}

impl Parse for Arguments {
    fn parse(tokens: ParseStream) -> Result<Arguments, syn::Error> {
        let name: proc_macro2::Ident = tokens.parse()?;
        let comma: Token![,] = tokens.parse()?;
        let signature: Signature = //tokens.parse()?;
            syn::item::parsing::parse_signature(tokens)?;

        Ok(Arguments {
            name: name,
            signature,
        })
    }
}

Unfortunately, the parse_signature call is in error:

error[E0603]: module `item` is private
   --> src/lib.rs:17:18
    |
17  |             syn::item::parsing::parse_signature(tokens)?;
    |                  ^^^^ private module
    |
note: the module `item` is defined here
   --> /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.33/src/lib.rs:363:1
    |
363 | mod item;
    | ^^^^^^^^^

What is the proper way to parse a Signature from a ParseStream?

Mutant Bob
  • 3,121
  • 2
  • 27
  • 52

2 Answers2

2

Why do you need a Signature? Depending on what you are actually trying to parse, you should use one of the following:

  • Fn* trait signature (ex. FnMut(usize) -> bool)

    Parse into syn::TraitBound (in order to catch lifetime bounds not present in a just a path), then you can get the inputs/output from the parenthesized arguments of the last segment of the trait bound's path.

  • Bare function, aka function pointer (ex. fn(usize) -> bool)

    Parse into syn::TypeBareFn, then you can get the inputs/output directly.

  • Function definition, including a body (ex. fn foo(x: usize) -> bool { x > 5 })

    Parse into syn::ItemFn, which includes a signature.

  • Foreign function definition (ex. fn foo(x: usize) -> bool)

    Parse into Struct syn::ForeignItemFn, which includes a signature. Note that this is meant for declarations in extern blocks, so chances are this is not actually what you are looking for.

Coder-256
  • 5,212
  • 2
  • 23
  • 51
  • TraitBound lacks a name for the function. I eventually worked around my problem by parsing an Ident and a TypeBareFn, but that makes the syntax a little inelegant. ItemFn includes a body, which would confuse the users of the macro. ForeignItemFn might be a good option, although it includes argument names which is not technically necessary, but might help folks reading the code later. – Mutant Bob Jul 29 '20 at 02:21
  • 1
    What exactly is your use case? I am worried that you may be going about this the wrong way. Can you update your question to provide an example input to the macro and how it would be used? – Coder-256 Jul 29 '20 at 03:03
  • I have added my idea for how the macro would be used. – Mutant Bob Jul 30 '20 at 16:55
2

I eventually found an example of how to work around this problem ( https://github.com/dtolnay/syn/blob/master/examples/lazy-static/lazy-static/src/lib.rs ). You are supposed to create your own struct and impl Parse for it. I was able to build my own syntax from Parseable elements.

struct MySignature {
    pub parameter_types: Vec<Type>,
}

impl Parse for MySignature {
    fn parse(tokens: ParseStream) -> Result<Self, syn::Error> {
        let mut parameter_types: Vec<Type> = Vec::new();

        let arg_types: ParseBuffer;
        parenthesized!(arg_types in tokens);

        while !arg_types.is_empty() {
            let arg_type: Type = arg_types.parse()?;
            parameter_types.push(arg_type);

            if !arg_types.is_empty() {
                let _comma: Token![,] = arg_types.parse()?;
            }
        }

        Ok(MySignature { parameter_types })
    }
}
Mutant Bob
  • 3,121
  • 2
  • 27
  • 52