2 个版本
0.0.2 | 2023年8月27日 |
---|---|
0.0.1 | 2023年8月26日 |
#123 in #diagnostics
110KB
1.5K SLoC
使过程宏中的错误报告变得简单易用
这个crate旨在使过程宏中的错误报告简单易用。尽可能减少工作量,从基于panic!
的错误迁移!
此外,您还可以显式地将一个空的标记流附加到您的错误中。
为此,这个crate围绕proc_macro::Diagnostic
和compile_error!
提供一个小型的适配器。它根据编译器的版本检测发出错误的最优方式。当底层诊断类型最终稳定时,这个crate将简单地将其委派给它,无需对您的代码进行任何更改!
因此,您可以使用这个crate并在稳定版之前提前拥有一些proc_macro::Diagnostic
功能,并确保您的错误报告代码面向未来。
[dependencies]
proc-macro-error = "1.0"
支持rustc 1.31及以上版本
快速示例
代码
#[proc_macro]
#[proc_macro_error]
pub fn make_fn(input: TokenStream) -> TokenStream {
let mut input = TokenStream2::from(input).into_iter();
let name = input.next().unwrap();
if let Some(second) = input.next() {
abort! { second,
"I don't like this part!";
note = "I see what you did there...";
help = "I need only one part, you know?";
}
}
quote!( fn #name() {} ).into()
}
这是在终端中渲染的错误
这是您的用户将在他们的IDE中看到的内容
示例
类似panic的用法
use proc_macro_error::{
proc_macro_error,
abort,
abort_call_site,
ResultExt,
OptionExt,
};
use proc_macro::TokenStream;
use syn::{DeriveInput, parse_macro_input};
use quote::quote;
// This is your main entry point
#[proc_macro]
// This attribute *MUST* be placed on top of the #[proc_macro] function
#[proc_macro_error]
pub fn make_answer(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
if let Err(err) = some_logic(&input) {
// we've got a span to blame, let's use it
// This immediately aborts the proc-macro and shows the error
//
// You can use `proc_macro::Span`, `proc_macro2::Span`, and
// anything that implements `quote::ToTokens` (almost every type from
// `syn` and `proc_macro2`)
abort!(err, "You made an error, go fix it: {}", err.msg);
}
// `Result` has some handy shortcuts if your error type implements
// `Into<Diagnostic>`. `Option` has one unconditionally.
more_logic(&input).expect_or_abort("What a careless user, behave!");
if !more_logic_for_logic_god(&input) {
// We don't have an exact location this time,
// so just highlight the proc-macro invocation itself
abort_call_site!(
"Bad, bad user! Now go stand in the corner and think about what you did!");
}
// Now all the processing is done, return `proc_macro::TokenStream`
quote!(/* stuff */).into()
}
proc_macro::Diagnostic
类似用法
use proc_macro_error::*;
use proc_macro::TokenStream;
use syn::{spanned::Spanned, DeriveInput, ItemStruct, Fields, Attribute , parse_macro_input};
use quote::quote;
fn process_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
attrs
.iter()
.filter_map(|attr| match process_attr(attr) {
Ok(res) => Some(res),
Err(msg) => {
emit_error!(attr, "Invalid attribute: {}", msg);
None
}
})
.collect()
}
fn process_fields(_attrs: &Fields) -> Vec<TokenStream> {
// processing fields in pretty much the same way as attributes
unimplemented!()
}
#[proc_macro]
#[proc_macro_error]
pub fn make_answer(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
let attrs = process_attrs(&input.attrs);
// abort right now if some errors were encountered
// at the attributes processing stage
abort_if_dirty();
let fields = process_fields(&input.fields);
// no need to think about emitted errors
// #[proc_macro_error] will handle them for you
//
// just return a TokenStream as you normally would
quote!(/* stuff */).into()
}
真实世界示例
structopt-derive
(类似abort的用法)auto-impl
(类似emit的用法)
限制
- 警告只在nightly版本中发出,在稳定版本中将被忽略。
- "帮助"建议在稳定版本中不能有自己的span信息(本质上是从父span继承的)。
- 如果您的宏触发了panic,则不会显示任何错误。这不是技术限制,而是有意的设计。
panic
不是用于错误报告的。
MSRV策略
proc_macro_error
将始终与过程宏的圣三一:proc_macro2
、syn
、quote
crate兼容。换句话说,如果圣三一可用 - proc_macro_error
也可用。
重要!
如果您想使用
#[proc_macro_error]
与synstructure
一起,您需要在decl_derive!
调用内部放置该属性。不幸的是,由于 pre-1.34 rustc 中的一些错误,在宏调用内部放置 proc-macro 属性不起作用,因此您的 MSRV 事实上是 1.34。
动机
proc-macro 中的错误处理很糟糕。目前的选择不多:您要么将错误“冒泡”到宏的顶层并转换为 compile_error!
调用,或者简单地使用老式的 panic。这两种方法都很糟糕
-
前者糟糕,因为对于会导致宏崩溃的临界错误,展开适当的错误处理显得非常冗余;因此,人们大多选择不为此费心,而是使用 panic。简单的
.expect
实在是太诱人了。此外,如果您决定在您的宏中实现这种基于
Result
的架构,您将不得不完全重写它,一旦proc_macro::Diagnostic
最终稳定。这并不酷。 -
后者糟糕,因为无法通过
panic!
将范围信息传递出去。rustc
将突出显示调用本身,但不会突出显示其中的一些特定标记。此外,panic 不是用于错误报告的;panic 是用于错误检测的(例如,在
None
上展开或越界索引)或早期开发阶段,您需要尽快得到原型,因此错误处理可以稍后进行。将这些用途混合在一起只会搞砸事情。 -
有一个
proc_macro::Diagnostic
,非常棒,但它已经实验性超过一年,不太可能很快稳定。这个crate的API故意设计成与
proc_macro::Diagnostic
兼容,并在可能的情况下将其委托给它。一旦Diagnostics
稳定,这个crate将始终将其委托给它,用户端不需要进行任何代码更改。
话虽如此,我们需要一个解决方案,但这个解决方案必须满足以下条件
- 它必须比
panic!
更好。主要观点:它必须提供一种将范围信息传递给用户的方法。 - 它必须尽可能少地努力才能从
panic!
迁移。理想情况下,一个新的宏,具有类似的语义,并能够执行范围信息。 - 它必须与
proc_macro::Diagnostic
兼容。 - 它必须在稳定版本中使用.
这个crate的目标是提供一个这样的机制。您需要做的只是用 #[proc_macro]
属性注释您的顶层 #[proc_macro_error]
函数,并将恐慌更改为适当的 abort!
/abort_call_site!
,请参阅指南。
免责声明
请注意,此crate不应用于除过程宏错误报告以外的任何用途,对于其他任何用途请使用Result
和?
(可能还需要使用许多现有的辅助函数之一)。
许可证
根据您的选择,此crate受Apache许可证第2版或MIT许可证的许可。除非您明确声明,否则根据Apache-2.0许可证的定义,您提交给此crate的任何有意贡献都将根据上述许可双授权,不附加任何额外条款或条件。
依赖项
~270–720KB
~17K SLoC