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(
            "#[mine(name = \"name\", tf = false)]",
println!("{:#?}", ts);
let _args: Vec<syn::NestedMeta> = ts
            // this fails:
            .map(|attr| attr.parse_args::<syn::NestedMeta>().unwrap())

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() {


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


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},
      let ts =
              r#"#[mine(name = "name", tf = false)]"#,
      // 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)
      println!("{:#?}", _args);

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.