You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 15:14:54