如何使用quote宏报告过程宏中的错误? [英] How to report errors in a procedural macro using the quote macro?

查看:127
本文介绍了如何使用quote宏报告过程宏中的错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个程序宏,该宏工作正常,但无法以符合人体工程学的方式报告错误。使用 panic!有效,但并不优雅,不会很好地向用户显示错误消息。

I am writing a procedural macro which works fine, but I am having trouble reporting errors in an ergonomic way. Using panic! "works" but is not elegant and does not present the error message to the user nicely.

我知道在解析 TokenStream 时可以报告良好的错误,但是在解析AST之后遍历AST时我需要产生错误。

I know that I can report good errors while parsing a TokenStream, but I need to produce errors while traversing the AST after it has been parsed.

宏调用看起来像这样:

attr_test! {
    #[bool]
    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 以外的任何内容,我想产生一个错误。例如,输入如下:

I would like to produce an error if anything other than bool is specified in the attribute. For example, input like this:

attr_test! {
    #[something_else]
    FOO
}

error: expected bool
attr_test! {
    #[something_else]
      ^^^^^^^^^^^^^^ expected bool
    FOO
}

在解析期间,有一个 Result ,其中包含许多有用的信息,包括 span ,因此产生的错误可以突出显示宏调用中有问题的确切部分。但是一旦遍历AST,就看不到报告错误的好方法。

During parsing, there is a Result, which has lots of useful information including a span, so the resulting errors can highlight the exact parts of the macro call that have a problem. But once I'm traversing the AST, I can't see a good way to report errors.

这应该怎么做?

推荐答案

除了恐慌之外,目前还有两种方法可以报告来自宏的错误: 不稳定的 Diagnostic API compile_error!技巧 。当前,主要使用后者,因为它可以稳定运行。让我们看看它们是如何工作的。

Apart from panicking, there are currently two ways to reports errors from a proc-macro: the unstable Diagnostic API and "the compile_error! trick". Currently, the latter is mostly used because it works on stable. Let's see how they both work.

自Rust 1.20起, compile_error! 宏存在于标准库中。它需要一个字符串,并在编译时导致错误。

Since Rust 1.20, the compile_error! macro exists in the standard library. It takes a string and leads to an error at compile time.

compile_error!("oopsie woopsie");

这会导致(游乐场):

error: oopsie woopsie
 --> src/lib.rs:1:1
  |
1 | compile_error!("oopsie woopsie");
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

已为两种情况添加了该宏: macro_rules!宏和#[cfg] 。在这两种情况下,如果用户错误使用宏或使用错误的 cfg 值,则库作者可以添加更好的错误。

This macro has been added for two cases: macro_rules! macros and #[cfg]. In both cases, library authors can add better errors if the user uses the macro incorrectly or has the wrong cfg values.

但是proc-macro程序员有一个有趣的主意。您可能知道,可以根据需要创建从程序宏返回的 TokenStream 。这包括这些标记的跨度:您可以将任何所需的跨度附加到输出标记。因此,主要思想是:

But proc-macro programmers had an interesting idea. As you might know, the TokenStream you return from your procedural macro can be created however you like. That includes the spans of those tokens: you can attach any spans you like to your output tokens. So the main idea is this:

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

Emit a tokenstream containing compile_error!("your error message"); but set the span of those tokens to the span of the input token that caused the error. There is even a macro in quote which makes this easier: quote_spanned!. In your case, we can write this:

let output = if ty.to_string() != "bool" {
    quote_spanned! {
        ty.span() =>
        compile_error!("expected bool");
    }
} else {
    quote! {
        const #name: #ty = false;
    }
};

对于您的错误输入,编译器现在将输出以下内容:

For your faulty input, the compiler now prints this:

error: expected bool
 --> examples/main.rs:4:7
  |
4 |     #[something_else]
  |       ^^^^^^^^^^^^^^

为什么这确实起作用?好吧: compile_error!的错误显示了包含 compile_error!调用的代码段。为此,使用了 compile_error!调用的范围。但是由于我们将跨度设置为指向错误的输入令牌 ty ,因此编译器显示了该令牌下划线的代码段。

Why exactly does this work? Well: the error for compile_error! shows the code snippet containing the compile_error! invocation. For that, the span of the compile_error! invocation is used. But since we set the span to point to the faulty input token ty, the compiler shows the snippet underlining that token.

syn 也使用此技巧来打印漂亮的错误。实际上,如果您仍然使用 syn ,则可以使用其 Error 类型,尤其是 Error :: to_compile_error 方法,它完全返回我们使用 quote_spanned!手动创建的令牌流:

This trick is also used by syn to print nice errors. In fact, if you are using syn anyway, you can use its Error type and in particular the Error::to_compile_error method which returns exactly the token stream we manually created with quote_spanned!:

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






诊断 API



As这仍然是不稳定的,只是一个简短的例子。诊断API比上面的技巧更强大,因为您可以具有多个范围,警告和注释。


The Diagnostic API

As this is still unstable, just a short example. The diagnostic API is more powerful than the trick above as you can have multiple spans, warnings and notes.

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

在该行之后,将显示错误,但是您仍然可以在proc-macro中进行操作。通常,您只会返回一个空的令牌流。

After that line, the error is printed, but you can still do stuff in your proc-macro. Usually, you would just return an empty token stream.

这篇关于如何使用quote宏报告过程宏中的错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆