Pet*_*all 10 error-handling rust rust-macros rust-proc-macros
我正在编写一个程序宏,它工作正常,但我无法以符合人体工程学的方式报告错误.使用panic!
"工作"但不优雅,并且不会很好地向用户显示错误消息.
我知道我可以在解析a时报告错误TokenStream
,但是在解析之后我需要在遍历AST时产生错误.
宏调用如下所示:
attr_test! {
#[bool]
FOO
}
Run Code Online (Sandbox Code Playgroud)
并应输出:
const FOO: bool = false;
Run Code Online (Sandbox Code Playgroud)
这是宏代码:
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")
}
}
Run Code Online (Sandbox Code Playgroud)
如果bool
属性中指定的内容以外的任何内容,我想产生错误.例如,输入如下:
attr_test! {
#[something_else]
FOO
}
Run Code Online (Sandbox Code Playgroud)
应该导致类似于:
error: expected bool
attr_test! {
#[something_else]
^^^^^^^^^^^^^^ expected bool
FOO
}
Run Code Online (Sandbox Code Playgroud)
在解析期间,有一个Result
包含a的大量有用信息span
,因此产生的错误可以突出显示有问题的宏调用的确切部分.但是一旦我遍历AST,我就看不到报告错误的好方法.
该怎么做?
Luk*_*odt 14
除了恐慌之外,目前有两种方法可以报告来自proc-macro的错误:不稳定的Diagnostic
API和" compile_error!
技巧".目前,后者主要用于稳定的.让我们看看它们是如何工作的.
compile_error!
绝招由于锈1.20,在compile_error!
宏存在于标准库.它需要一个字符串并在编译时导致错误.
compile_error!("oopsie woopsie");
Run Code Online (Sandbox Code Playgroud)
这导致(游乐场):
error: oopsie woopsie
--> src/lib.rs:1:1
|
1 | compile_error!("oopsie woopsie");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
这个宏已添加两种情况:macro_rules!
宏和#[cfg]
.在这两种情况下,如果用户错误地使用宏或具有错误的cfg
值,库作者可以添加更好的错误.
但proc-macro程序员有一个有趣的想法.您可能知道,TokenStream
您可以根据自己的喜好创建从程序宏返回的内容.这包括这些令牌的跨度:您可以将任何您喜欢的跨度附加到输出令牌.所以主要的想法是这样的:
发出包含compile_error!("your error message");
令牌流的令牌流,但将这些令牌的跨度设置为导致错误的输入令牌的跨度.甚至有一个宏quote
可以让这更容易:quote_spanned!
.在你的情况下,我们可以这样写:
let output = if ty.to_string() != "bool" {
quote_spanned! {
ty.span() =>
compile_error!("expected bool");
}
} else {
quote! {
const #name: #ty = false;
}
};
Run Code Online (Sandbox Code Playgroud)
对于错误的输入,编译器现在打印出:
error: expected bool
--> examples/main.rs:4:7
|
4 | #[something_else]
| ^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
为什么这确实有效?好吧:错误compile_error!
显示包含compile_error!
调用的代码片段.为此,compile_error!
使用调用的范围.但是,由于我们将跨度设置为指向错误的输入标记ty
,因此编译器会显示该标记的下划线.
这个技巧也用于syn
打印漂亮的错误.事实上,如果你正在syn
使用它,你可以使用它的Error
类型,特别是返回我们手动创建的令牌流的Error::to_compile_error
方法quote_spanned!
:
syn::Error::new(ty.span(), "expected bool").to_compile_error()
Run Code Online (Sandbox Code Playgroud)
Diagnostic
API由于这仍然不稳定,只是一个简短的例子.诊断API比上面的技巧更强大,因为您可以有多个跨度,警告和注释.
Diagnostic::spanned(ty.span().unwrap(), Level::Error, "expected bool").emit();
Run Code Online (Sandbox Code Playgroud)
在该行之后,将打印错误,但您仍然可以在proc-macro中执行操作.通常,您只需返回一个空令牌流.
接受的答案提到了不稳定的Diagnostic
API,它为您提供了比常规compile_error
. 在Diagnostic
API 稳定之前(可能不会很快),您可以使用该proc_macro_error
crate。它提供了一种Diagnostic
设计为与不稳定的 API 兼容的类型proc_macro::Diagnostic
。整个API没有实现,只实现了stable上可以合理实现的部分。您只需将提供的注释添加到宏中即可使用它:
#[proc_macro_error]
#[proc_macro]
fn my_macro(input: TokenStream) -> TokenStream {
// ...
Diagnostic::spanned(ty.span().unwrap(), Level::Error, "expected bool").emit();
}
Run Code Online (Sandbox Code Playgroud)
proc_macro_error
还提供了一些有用的宏来发出错误:
abort! { input,
"I don't like this part!";
note = "A notice message...";
help = "A help message...";
}
Run Code Online (Sandbox Code Playgroud)
但是,您可能需要考虑坚持使用该Diagnostic
类型,因为当它稳定时,它可以更轻松地迁移到官方Diagnostic
API。