Syn: match either one or another

I'm writing a procedural macro attribute and want to add some configuration to it. It will consist of two parts, both of them optional:

  • a flag added by simply stating its name,
  • a string (in fact, identifier) parameter, added as prop = ident part.

For now, I'm able to parse this if there are no config parameters, if there is only one of them, or if thy are set in particular order. But I'd like to make them also acceptable in reverse order, and that's where I'm blocked.

The code in question looks like following:

use syn::{
    parse::{Parse, ParseStream},
    Result, Token,
};

struct RawConfigFlagFirst {
    _flag: Flag,
    name: Option<Name>,
}
struct RawConfigNameFirst {
    name: Name,
    _flag: Option<Flag>,
}

struct Flag;
struct Name(String);

enum RawConfig {
    FlagFirst(RawConfigFlagFirst),
    NameFirst(RawConfigNameFirst),
}

impl Parse for Flag {
    fn parse(stream: ParseStream) -> Result<Self> {
        let ident: syn::Ident = stream.parse()?;
        if ident.to_string().as_str() == "flag" {
            Ok(Flag)
        } else {
            Err(stream.error("Unexpected token"))
        }
    }
}
impl Parse for Name {
    fn parse(stream: ParseStream) -> Result<Self> {
        let ident: syn::Ident = stream.parse()?;
        if ident.to_string().as_str() == "name" {
            let _: Token![=] = stream.parse()?;
            let name: syn::Ident = stream.parse()?;
            Ok(Name(name.to_string()))
        } else {
            Err(stream.error("Unexpected token"))
        }
    }
}
impl Parse for RawConfigFlagFirst {
    fn parse(stream: ParseStream) -> Result<Self> {
        Ok(RawConfigFlagFirst {
            _allow_self_sized: stream.parse()?,
            name: if stream.is_empty() {
                None
            } else {
                let _: Token![,] = stream.parse()?;
                Some(stream.parse()?)
            },
        })
    }
}
impl Parse for RawConfigNameFirst {
    fn parse(stream: ParseStream) -> Result<Self> {
        Ok(RawConfigNameFirst {
            name: stream.parse()?,
            allow_self_sized: if stream.is_empty() {
                None
            } else {
                let _: Token![,] = stream.parse()?;
                Some(stream.parse()?)
            },
        })
    }
}
impl Parse for RawConfig {
    fn parse(stream: ParseStream) -> Result<Self> {
        let streaf = stream.fork();
        match (
            streaf.parse::<RawConfigFlagFirst>(),
            stream.parse::<RawConfigNameFirst>(),
        ) {
            (Ok(cfg), _) => Ok(RawConfig::FlagFirst(cfg)),
            (Err(_), Ok(cfg)) => Ok(RawConfig::NameFirst(cfg)),
            (Err(mut err1), Err(err2)) => {
                err1.combine(err2);
                Err(err1)
            }
        }
    }
}

Then, if I try to parse the string flag, name = name, I get the Unexpected token error. Looks like that this is due to the fact that stream in RawConfig parsing function is not exhausted, since it tries to parse the wrong structure. Am I doing this somehow systematically wrong, or just don't see some solution in this approach?

Not actually a specific answer to your problem with syn but I'd look at the darling crate, which makes things like this pretty easy:

extern crate proc_macro;
use syn::export::{TokenStream, TokenStream2, quote::quote};
use syn::{parse_macro_input, AttributeArgs};
use darling::FromMeta;

#[derive(Debug, FromMeta)]
struct MetaArgs {
    #[darling(default)]
    flag: bool,
    #[darling(default)]
    name: Option<String>,
}

#[proc_macro_attribute]
pub fn some_attr(args: TokenStream, item: TokenStream) -> TokenStream {
    let attr_args = parse_macro_input!(args as AttributeArgs);

    let MetaArgs { flag, name } = match FromMeta::from_list(&attr_args) {
        Ok(v) => v,
        Err(e) => {
            return e.write_errors().into();
        }
    };

    println!("flag: {} name: {:?}", flag, name);
    item
}

// flag: false name: Some("hello") 
#[some_attr(name = "hello")]
fn foo() {}

// flag: true name: None
#[some_attr(flag)]
fn bar() {}

Thanks, looks like exactly the thing I need! This is not an exercise (well, it is, but not necessary that deep one), so using the library like this is absolutely OK.

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