Error handling 如何使用quote宏报告过程宏中的错误?

Error handling 如何使用quote宏报告过程宏中的错误?,error-handling,rust,rust-macros,rust-proc-macros,Error Handling,Rust,Rust Macros,Rust Proc Macros,我正在写一个程序宏,它工作得很好,但我在以符合人体工程学的方式报告错误时遇到了困难。使用恐慌“起作用”,但并不优雅,也不会很好地向用户显示错误消息 我知道在解析令牌流时可以报告良好的错误,但在解析AST后遍历AST时需要产生错误 宏调用如下所示: attr_test! { #[bool] FOO } attr_test! { #[something_else] FOO } 并应输出: const FOO: bool = false; 这是宏代码: exter

我正在写一个程序宏,它工作得很好,但我在以符合人体工程学的方式报告错误时遇到了困难。使用
恐慌“起作用”,但并不优雅,也不会很好地向用户显示错误消息

我知道在解析
令牌流时可以报告良好的错误,但在解析AST后遍历AST时需要产生错误

宏调用如下所示:

attr_test! {
    #[bool]
    FOO
}
attr_test! {
    #[something_else]
    FOO
}
并应输出:

const FOO: bool = false;
这是宏代码:

extern crate proc_macro;
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::{Attribute, parse_macro_input, Ident, Meta};

struct AttrTest {
    attributes: Vec<Attribute>,
    name: Ident,
}

impl Parse for AttrTest {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(AttrTest {
            attributes: input.call(Attribute::parse_outer)?,
            name: input.parse()?,
        })
    }
}

#[proc_macro]
pub fn attr_test(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let test: AttrTest = parse_macro_input!(tokens);
    let name = test.name;
    let first_att = test.attributes
        .get(0)
        .and_then(|att| att.parse_meta().ok());
    if let Some(Meta::Word(ty)) = first_att {
        if ty.to_string() != "bool" {
            panic!("expected bool");
        }
        let output = quote! {
            const #name: #ty = false;
        };
        output.into()
    } else {
        panic!("malformed or missing metadata")
    }
}
结果应该是:

错误:预期bool
属性测试!{
#[其他东西]
^^^^^^^^^^^^^^期望布尔
福
}
在解析过程中,有一个
结果
,其中包含大量有用的信息,包括
span
,因此产生的错误可以突出显示宏调用中存在问题的确切部分。但是一旦我遍历AST,我就看不到报告错误的好方法


如何做到这一点?

除了恐慌之外,目前有两种方法可以报告proc宏中的错误:和“编译错误!
技巧”。目前,后者主要使用,因为它在稳定的环境下工作。让我们看看它们是如何工作的

编译错误技巧
因为生锈1.20。它接受字符串并在编译时导致错误

compile_error!("oopsie woopsie");
这导致()的出现:

错误:oopsie woopsie
-->src/lib.rs:1:1
|
1 |编译错误!(oopsie woopsie);;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
添加此宏用于两种情况:
macro\u规则宏和
#[cfg]
。在这两种情况下,如果用户错误地使用宏或具有错误的
cfg
值,库作者可以添加更好的错误

但是proc宏程序员有一个有趣的想法。如您所知,您可以随意创建从过程宏返回的
TokenStream
。这包括这些标记的范围:您可以将任何您喜欢的范围附加到输出标记。所以主要的想法是:

发出包含
编译错误的令牌流!(“您的错误消息”)
但将这些标记的范围设置为导致错误的输入标记的范围。
甚至在
quote
中有一个宏,这使得操作更简单:。在您的情况下,我们可以这样写:

let output = if ty.to_string() != "bool" {
    quote_spanned! {
        ty.span() =>
        compile_error!("expected bool");
    }
} else {
    quote! {
        const #name: #ty = false;
    }
};
对于错误的输入,编译器现在打印以下内容:

错误:预期bool
-->示例/main.rs:4:7
|
4 |#[其他东西]
|       ^^^^^^^^^^^^^^
这到底是为什么?嗯:
compile\u错误显示包含
编译\u错误的代码段调用。为此,
compile\u错误的范围调用。但是,由于我们将span设置为指向错误的输入标记
ty
,编译器将在该标记下面显示代码段

syn
也使用此技巧打印漂亮的错误。事实上,如果您使用的是
syn
类型,那么您可以使用它的
Error
类型,尤其是返回我们使用
quote\u span手动创建的令牌流

syn::Error::new(ty.span(), "expected bool").to_compile_error()

诊断
API 由于这仍然不稳定,所以仅举一个简短的例子。诊断API比上述技巧更强大,因为您可以有多个跨度、警告和注释

Diagnostic::spanned(ty.span().unwrap(), Level::Error, "expected bool").emit();

在这一行之后,错误被打印出来,但是您仍然可以在proc宏中执行某些操作。通常,您只会返回一个空的令牌流。

公认的答案提到了不稳定的
诊断
API,它比常规的
编译错误
提供了更多的功能和控制。在诊断API稳定之前,您可以使用板条箱。它提供的类型旨在与不稳定的
proc\u macro::Diagnostic
兼容。整个API并没有实现,只有可以在stable上合理实现的部分。只需将提供的注释添加到宏中即可使用:

#[proc_macro_error]
#[程序宏]
fn my_宏(输入:TokenStream)->TokenStream{
// ...
诊断::跨距(ty.span().unwrap(),级别::错误,“预期bool”).emit();
}
proc\u macro\u error
还提供了一些用于发出错误的有用宏:

中止!{输入,
“我不喜欢这部分!”;
注意=“通知消息…”;
help=“帮助信息…”;
}

但是,您可能需要考虑坚持使用<代码>诊断< /代码>类型,因为当它稳定时,它会更容易迁移到官方的代码>诊断< /代码> API。