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

如何让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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 08:48:12