#proc-macro #error #spans #diagnostics #panic #stable #point

已删除 proc-macro-error2

几乎可以完全替换过程宏中的panic

2 个版本

0.0.2 2023年8月27日
0.0.1 2023年8月26日

#123 in #diagnostics

MIT/Apache

110KB
1.5K SLoC

使过程宏中的错误报告变得简单易用

travis ci docs.rs unsafe forbidden

这个crate旨在使过程宏中的错误报告简单易用。尽可能减少工作量,从基于panic!的错误迁移!

此外,您还可以显式地将一个空的标记流附加到您的错误中。

为此,这个crate围绕proc_macro::Diagnosticcompile_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()
}

真实世界示例

限制

  • 警告只在nightly版本中发出,在稳定版本中将被忽略。
  • "帮助"建议在稳定版本中不能有自己的span信息(本质上是从父span继承的)。
  • 如果您的宏触发了panic,则不会显示任何错误。这不是技术限制,而是有意的设计。panic不是用于错误报告的。

MSRV策略

proc_macro_error将始终与过程宏的圣三一:proc_macro2synquote 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