如何使用 syn v2 解析这样的属性管:`#[attr("a", "b", "c")]`?

use*_*481 7 rust syn

synv1中,有NestedMeta一个对于解析嵌套元非常方便的功能。但自synv2 以来,它已被删除。

例如,

trait Hello {
    fn hello();
}

#[derive(Hello)]
#[hello("world1", "world2")]
struct A;

fn main() {
    A::hello();
}
Run Code Online (Sandbox Code Playgroud)

我希望上面的代码打印Hello world1, world2!在屏幕上。我的 proc-macro 可以使用 v1 来实现,syn如下所示

use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, Meta};

#[proc_macro_derive(Hello, attributes(hello))]
pub fn hello_derive(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();

    let name = &ast.ident;

    let mut target: Vec<String> = vec![];

    for attr in ast.attrs {
        if let Some(attr_meta_name) = attr.path.get_ident() {
            if attr_meta_name == "hello" {
                let attr_meta = attr.parse_meta().unwrap();

                match attr_meta {
                    Meta::List(list) => {
                        for p in list.nested {
                            match p {
                                NestedMeta::Lit(lit) => match lit {
                                    Lit::Str(lit) => {
                                        target.push(lit.value());
                                    },
                                    _ => {
                                        panic!("Incorrect format for using the `hello` attribute.")
                                    },
                                },
                                NestedMeta::Meta(_) => {
                                    panic!("Incorrect format for using the `hello` attribute.")
                                },
                            }
                        }
                    },
                    _ => panic!("Incorrect format for using the `hello` attribute."),
                }
            }
        }
    }

    if target.is_empty() {
        panic!("The `hello` attribute must be used to set at least one target.");
    }

    let target = target.join(", ");

    let expanded = quote! {
        impl Hello for #name {
            fn hello() {
                println!("Hello {}.", #target);
            }
        }
    };

    expanded.into()
}
Run Code Online (Sandbox Code Playgroud)

但是当我尝试用synv2 重新实现它时,我坚持使用parse_nested_meta似乎拒绝文字的方法。

use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, Meta};

#[proc_macro_derive(Hello, attributes(hello))]
pub fn hello_derive(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();

    let name = &ast.ident;

    let mut target: Vec<String> = vec![];

    for attr in ast.attrs {
        if let Some(attr_meta_name) = attr.path().get_ident() {
            if attr_meta_name == "hello" {
                let attr_meta = attr.meta;

                match attr_meta {
                    Meta::List(list) => {
                        list.parse_nested_meta(|meta| {
                            // I don't know how to handle this
                            Ok(())
                        })
                        .unwrap();
                    },
                    _ => panic!("Incorrect format for using the `hello` attribute."),
                }
            }
        }
    }

    if target.is_empty() {
        panic!("The `hello` attribute must be used to set at least one target.");
    }

    let target = target.join(", ");

    let expanded = quote! {
        impl Hello for #name {
            fn hello() {
                println!("Hello {}.", #target);
            }
        }
    };

    expanded.into()
}
Run Code Online (Sandbox Code Playgroud)

如何使用synv2解析属性?

use*_*481 1

似乎没有其他实现该特征的内置结构Parse可以做到这一点。所以我们必须自己创建一个。

use proc_macro::TokenStream;
use quote::quote;
use syn::{
    parse::{Parse, ParseStream},
    DeriveInput, LitStr, Meta, Token,
};

struct MyParser {
    v: Vec<String>,
}

impl Parse for MyParser {
    #[inline]
    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
        let mut v: Vec<String> = vec![];

        loop {
            if input.is_empty() {
                break;
            }

            v.push(input.parse::<LitStr>()?.value());

            if input.is_empty() {
                break;
            }

            input.parse::<Token!(,)>()?;
        }

        Ok(MyParser {
            v,
        })
    }
}

#[proc_macro_derive(Hello, attributes(hello))]
pub fn hello_world_derive(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();

    let name = &ast.ident;

    let mut target: Vec<String> = vec![];

    for attr in ast.attrs {
        if attr.path().is_ident("hello") {
            match attr.meta {
                Meta::List(list) => {
                    let parsed: MyParser = list.parse_args().unwrap();

                    target.extend_from_slice(&parsed.v);
                },
                _ => panic!("Incorrect format for using the `hello` attribute."),
            }
        }
    }

    if target.is_empty() {
        panic!("The `hello` attribute must be used to set at least one target.");
    }

    let target = target.join(", ");

    let expanded = quote! {
        impl Hello for #name {
            fn hello() {
                println!("Hello {}.", #target);
            }
        }
    };

    expanded.into()
}
Run Code Online (Sandbox Code Playgroud)