Proc-macro-attribute parse_args: Error("unexpected token")

I'm trying to setup unit tests for some functions used in a proc-macro-attribute. It is proving quite hard to replicate syn::parse_macro_input!(args as AttributeArgs) which is what is passed to the functions.

I expected this to work, but cannot see why parse_args fails with Error("unexpected token"):

let ts: Vec<syn::Attribute> = syn::parse::Parser::parse_str(
            syn::Attribute::parse_outer,
            "#[mine(name = \"name\", tf = false)]",
        )
        .unwrap();
println!("{:#?}", ts);
let _args: Vec<syn::NestedMeta> = ts
            .iter()
            // this fails:
            .map(|attr| attr.parse_args::<syn::NestedMeta>().unwrap())
            .collect();

You can try this:

use syn::{parse::Parser, Attribute, Result};

fn main() -> Result<()> {
    let attrs = Attribute::parse_outer.parse_str("#[mine(name = \"name\", tf = false)]")?;
    let args = attrs.iter().map(Attribute::parse_meta).collect::<Result<Vec<_>>>()?;

    if let Some(syn::Meta::List(syn::MetaList { nested, .. })) = args.first() {
        dbg!(nested);
    }

    Ok(())
}

nested is what you want, because Punctuated<NestedMeta, Comma> is just like Vec<syn::NestedMeta> in some way.

3 Likes

One NestedMeta represents something like name = "name" or tf = false, but a combination of those two, comma-separated, requires expressing that extra property. In syn, this involves the Punctuated<Element, Separator> type, and then a parsing flavor (either trailing separators and empty sequences are tolerated (parse_terminated flavor), or neither are (parse_separated_nonempty, for rarer use cases)).

Finally, you have to realize that

#[mine(name = "name")]
#[mine(tf = false)]

will probably have to yield equivalent semantics to your use case. So you have two dimensions to iterate through, and yet want to compile a single-dimensional Vec. It's time to .flatten() (or, since we already have a .map(), to .flat_map() instead). Hence yielding:

  fn main ()
  {
      use ::syn::{*,
          parse::{Parse, Parser, ParseStream},
          punctuated::Punctuated,
          spanned::Spanned,
          Result,
      };
  
      let ts =
          parse::Parser::parse_str(
              Attribute::parse_outer,
              r#"#[mine(name = "name", tf = false)]"#,
          )
          .unwrap()
      ;
      // println!("{:#?}", ts);
      let _args: Vec<NestedMeta> =
          ts  .iter()
-             .map(|attr| {
-                 attr.parse_args::<NestedMeta>()
+             .flat_map(|attr| {
+                 attr.parse_args_with(Punctuated::<NestedMeta, Token![,]>::parse_terminated)
                      .unwrap()
              })
              .collect()
      ;
      println!("{:#?}", _args);
  }
3 Likes

Thank you both @vague and @Yandros I learnt a lot from both approaches.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.