如何让Rust编译错误指向展开后的proc macro代码?
让Proc Macro编译错误指向展开后代码的解决方案
我希望proc macro的编译错误能指向展开后的代码,但目前错误仅指向派生宏的外层调用行或属性行。以下是相关代码及当前错误信息:
相关代码
Proc Macro代码
fn get_field_attribute( attributes: &Vec<syn::Attribute>, literal_vec: &Vec<String>, attri_ident: &str, token_ident: &str, punct_char: char, ) -> (Option<String>, Option<proc_macro2::TokenStream>) { let meta = &attributes[0].meta; if let syn::Meta::List(nvm) = meta { let syn::MetaList { path, tokens, .. } = nvm; if path.is_ident(attri_ident) { //"builder" let mut tokens_iterator = tokens.clone().into_iter(); let expect_each = tokens_iterator.next().unwrap(); if let proc_macro2::TokenTree::Ident(ref ident) = expect_each { if ident.to_string() != token_ident { return ( None, Some( syn::Error::new_spanned(nvm, "expected `builder(each = \"...\")`") .to_compile_error(), ), ); } else { // 剩余代码省略 } } } } // 剩余代码省略 (None, None) }
宏库Cargo.toml
[package] name = "mymacro" version = "0.1.0" edition = "2024" [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.93" quote = "1.0.38" syn = {version = "2.0.98", features = ["extra-traits"]}
调用宏的二进制代码
use mymacro::MyMacro; #[derive(MyMacro)] pub struct Command { executable: String, #[builder(eac = "arg")] // 此处故意写错`each`为`eac` args: Vec<String>, env: Vec<String>, current_dir: Option<String>, }
当前错误信息
error: expected `builder(each = "...")` --> mytest/src/main.rs:5:7 | 5 | #[builder(eac = "arg")] | ^^^^^^^^^^^^^^^^^^^^
解决方案
需要明确两种错误场景的处理方式:
场景1:宏生成代码本身存在编译错误(如类型不匹配、变量未定义)
默认情况下,proc宏生成的代码会继承宏调用的span,导致错误指向#[derive(MyMacro)]行。要让错误指向展开后的代码,可通过以下方式:
方法1:使用
quote_spanned指定span
在生成代码时,使用quote::quote_spanned!将生成代码的span关联到展开后的代码位置。例如,若要让生成的某个变量的错误指向展开后的代码,可使用proc_macro2::Span::call_site()作为span参数:use quote::quote_spanned; let generated_code = quote_spanned!(proc_macro2::Span::call_site() => { // 生成的代码内容 let invalid_var = 123; invalid_var.not_a_method(); // 故意制造错误 });编译时,错误会指向展开后的代码中
invalid_var.not_a_method()的位置。方法2:编译时查看展开后的代码错误
使用cargo rustc -- --pretty expanded命令编译,编译器会输出展开后的完整代码,并在对应位置标注错误。
场景2:宏解析用户输入时的错误(如属性参数写错)
这类错误本质是用户输入不符合要求,错误应该指向用户编写的代码(如属性行),而非展开后的代码。你当前的错误就属于这种情况,现有错误提示是合理的。如果确实需要将这类错误关联到展开后的代码(不推荐,因为用户无法直接修改展开后的代码),可以手动创建错误时指定展开后代码的span:
// 假设你已经生成了展开后的代码片段 let expanded_code = quote! { // 展开后的代码中对应属性处理的部分 fn build_args(&mut self, arg: String) -> &mut Self { self.args.push(arg); self } }; // 创建错误并关联到展开后的代码span let error = syn::Error::new_spanned(expanded_code, "属性参数错误:应为`each`而非`eac`") .to_compile_error(); return (None, Some(error));
注意事项
- 通常推荐让错误指向用户编写的原始代码,这样用户能直接定位并修改问题。
- 若需要调试宏生成的代码,可使用
cargo expand命令查看完整的展开结果。
内容的提问来源于stack exchange,提问作者Jiu hong
相关产品推荐
相关产品推荐

