如何使用syn v2解析形如#[attr("a","b","c")]的属性?
如何用syn v2解析嵌套字面量属性
在syn v1中可以用NestedMeta类型便捷解析嵌套元数据,但syn v2移除了这个类型。我们以一个proc-macro示例说明:
目标是让以下代码运行后打印Hello world1, world2!:
trait Hello { fn hello(); } #[derive(Hello)] #[hello("world1", "world2")] struct A; fn main() { A::hello(); }
syn v1的实现
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() }
syn v2的问题
尝试用syn v2重写时,误用了parse_nested_meta方法——这个方法是用来解析带键的嵌套元数据(比如hello(foo="bar")),无法直接处理纯字面量列表,导致卡壳:
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| { // 不知道怎么处理字面量 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() }
解决方法:直接遍历嵌套元素
syn v2中依然保留了对嵌套字面量的支持,只是API细节有变化:不需要用parse_nested_meta,直接遍历MetaList的nested字段即可,和v1的逻辑一致,只是部分类型的路径和方法名有调整:
use proc_macro::TokenStream; use quote::quote; use syn::{DeriveInput, Lit, Meta, NestedMeta}; #[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" { match attr.meta { Meta::List(list) => { for nested in list.nested { match nested { NestedMeta::Lit(lit) => match lit { Lit::Str(s) => target.push(s.value()), _ => panic!("`hello` attribute only accepts string literals"), }, NestedMeta::Meta(_) => panic!("`hello` attribute does not accept nested metadata"), } } }, _ => panic!("`hello` attribute must be a list of string literals"), } } } } if target.is_empty() { panic!("At least one target must be specified with the `hello` attribute"); } let target_str = target.join(", "); let expanded = quote! { impl Hello for #name { fn hello() { println!("Hello {}.", #target_str); } } }; expanded.into() }
关键调整点:
- 直接遍历
list.nested,而非调用parse_nested_meta——后者用于解析键值对形式的嵌套元数据,不适合纯字面量场景 - syn v2中
attr.path()是方法而非字段,attr.meta直接获取元数据(无需调用parse_meta()) - 类型路径保持一致:
NestedMeta::Lit和Lit::Str依然可用,逻辑和v1完全匹配
内容的提问来源于stack exchange,提问作者user3032481
相关产品推荐
相关产品推荐

