诊断和子诊断结构体
原文来自《Rustc 开发指南》:Diagnostic and subdiagnostic structs
rustc 有两个诊断派生宏 (diagnostic derives),可用于创建简单的诊断,建议在合适的地方使用:
#[derive(SessionDiagnostic)] 和 #[derive(SessionSubdiagnostic)]。
使用派生宏创建的诊断可以翻译成不同的语言,并且每种语言都有一个唯一标识诊断的路径 (slug)。
SessionDiagnostic
除了使用 DiagnoticBuilder 接口创建和发出诊断外,还可以使用 SessionDiagnotic 派生宏。
#[derive(SessionDiagnotics)] 只适用于不需要太多逻辑来决定是否添加附加子诊断的简单诊断。
示例
考虑如下所示的 field already declared 的定义:
#[derive(SessionDiagnostic)]
#[error(typeck::field_already_declared, code = "E0124")]
pub struct FieldAlreadyDeclared {
pub field_name: Ident,
#[primary_span]
#[label]
pub span: Span,
#[label = "previous-decl-label"]
pub prev_span: Span,
}
SessionDiagnostic 只能应用于结构体。每个 SessionDiagnostic 都必须有一个属性应用于结构本身:
- 要么使用
#[error(...)]定义错误 - 要么使用
#[warning(...)]定义警告
如果错误具有错误代码(例如“E0624”),则可以使用 code 子属性指定。
指定 code 不是必需的,但如果你要移植的诊断使用 DiagnoticBuilder,那么如果有错误码的话,使用
SessionDiagnostic 时应保留错误码。
#[error(...)] 和 #[warning(...)] 都必须提供一个路径 (slug) 作为第一个位置参数(即一个通向
rustc_errors::fluent::* 中的条目的路径)。
slug 唯一地标识了诊断,也是编译器知道要发出什么错误消息的方式(在编译器的默认 locale 中,或者在用户要求的 locale)。有关如何编写可翻译的错误消息以及如何生成 slug 条目的详细信息,请参阅 translation。
在我们的示例中,field already declared 诊断的 Fluent 消息如下所示:
typeck-field-already-declared =
field `{$field_name}` is already declared
.label = field already declared
.previous-decl-label = `{$field_name}` first declared here
typeck-field-already-declared 是示例中的 slug,后面跟着诊断消息。
没有标注属性的 SessionDiagnostic 的每个字段都可以作为 Fluent 消息中可用的变量,例子中的
field_name。如果不需要,可以将字段标为 #[skip_arg]。
在类型为 Span 的字段上使用 #[primary_span] 属性表示诊断的主要范围,该范围将包含诊断的主要消息。
诊断不仅仅有它们的主要消息,通常还包括标签 (labels)、注意 (notes)、帮助 (help) 消息和建议
(suggestions),所有这些也可以在 SessionDiagnotic 中指定。
#[label]、#[help]、#[note] 都可以应用于 Span 类型的字段。
应用这些属性中的任何一个都将创建该 Span 相应的子诊断。这些属性会在主 Fluent 消息相关的 Fluent
属性中查找它们的诊断消息。示例中的 #[label] 将查找 typeck-field-already-declared.label
(其消息为 “fieldAlternated”)。
如果有多个相同类型的子诊断,则这些属性还可以接收要查找的属性名称(例如,示例中的 prese-decl-label)。
以下类型在使用 SessionDiagnotic 派生宏时有特殊行为:
- 所有应用于
Option<T>的属性,仅当值为Some(...)时才会发出子诊断 - 所有应用于
Vec<T>的属性,将针对其每个元素重复
#[help] 和 #[note] 也可以应用于结构体本身,此时,它们的工作方式与应用于字段时完全相同,只是子诊断不会有
Span。它们应用于 () 类型的字段也是一样的效果:当与 Option 类型组合时,表示可选的 #[note]/#[help] 子诊断。
可使用以下四个字段属性之一发出建议:
#[suggestion(message = "...", code = "...", applicability = "...")]#[suggestion_hidden(message = "...", code = "...", applicability = "...")]#[suggestion_short(message = "...", code = "...", applicability = "...")]#[suggestion_verbose(message = "...", code = "...", applicability = "...")]
#[suggeston*] 必须应用于 Span 字段或 (Span, MachineApplicability) 字段。与其他字段属性类似:
message在消息中指定了 Fluent 属性,默认为.suggestioncode指定了建议替换的代码,并且是一个格式字符串,例如{field_name}将被结构体的field_name字段的值替换,而不是 Fluent 的标识符applicability指定了属性中的适用性,当字段的类型包含Applicablity时不能使用
最后, SessionDiagnotic 派生宏将生成一个 SessionDiagnotic 的实现,如下所示:
impl SessionDiagnostic for FieldAlreadyDeclared {
fn into_diagnostic(self, sess: &'_ rustc_session::Session) -> DiagnosticBuilder<'_> {
let mut diag = sess.struct_err_with_code(
rustc_errors::DiagnosticMessage::fluent("typeck-field-already-declared"),
rustc_errors::DiagnosticId::Error("E0124")
);
diag.set_span(self.span);
diag.span_label(
self.span,
rustc_errors::DiagnosticMessage::fluent_attr("typeck-field-already-declared", "label")
);
diag.span_label(
self.prev_span,
rustc_errors::DiagnosticMessage::fluent_attr("typeck-field-already-declared", "previous-decl-label")
);
diag
}
}
我们已经定义了自己的诊断,那如何使用它呢?非常简单,只需创建结构体的一个实例并将其传递给 emit_err(或 emit_warning):
tcx.sess.emit_err(FieldAlreadyDeclared {
field_name: f.ident,
span: f.span,
prev_span,
});
属性
#[derive(SessionDiagnostic)] 支持以下属性:
#[error | warning]
#[error(slug, code = "...")] 或 #[warning(slug, code = "...")]:
- 应用于结构体
- 强制需要的
- 定义了结构体表示错误或警告
- Slug:强制需要
- 唯一标识诊断,并与其 Fluent 消息相对应
- 通向
rustc_errors::fluent中的条目的路径- 在模块中总是以 Fluent 的资源名称开头,通常是诊断所在 crate 的名字
- 例如
rustc_errors::fluent::typeck::field_already_declared,其中rustc_errors::fluent在属性中隐含,所以只需要typeck::field_already_declared
- 见 translation
code = "...":可选的- 指定错误码
#[note]
#[note] 或 #[note = "..."] (可选的)
- 应用于结构体或
Span/()字段 - 增加一个用于注意的子诊断
- 值是 note 消息的 Fluent 属性(相对于被
slug指定的 Fluent 消息来说)- 默认是 Fluent 的
note属性
- 默认是 Fluent 的
- 如果应用到
Span字段,则创建一个 spanned note
#[help]
#[help] 或 #[help = "..."] (可选的)
- 应用于结构体或
Span/()字段 - 增加一个提供帮助信息的子诊断
- 值是 help 消息的 Fluent 属性(相对于被
slug指定的 Fluent 消息来说)- 默认是 Fluent 的
help属性
- 默认是 Fluent 的
- 如果应用到
Span字段,则创建一个 spanned help
#[label]
#[label] 或 #[label = "..."] (可选的)
- 应用于
Span字段 - 增加一个标签子诊断
- 值是 label 消息的 Fluent 属性(相对于被
slug指定的 Fluent 消息来说)- 默认是 Fluent 的
label属性
- 默认是 Fluent 的
#[suggestion*]
#[suggestion{,_hidden,_short,_verbose}(message = "...", code = "...", applicability = "...")] (可选的)
- 应用于
(Span, MachineApplicability)或Span字段 - 增加一个建议子诊断
message = "..."(强制需要)- 值是 suggestion 消息的 Fluent 属性(相对于被
slug指定的 Fluent 消息来说) - 默认是 Fluent 的
suggestion属性
- 值是 suggestion 消息的 Fluent 属性(相对于被
code = "..."(强制需要)- 值是建议替换的代码的格式字符串
applicability = "..."(可选的)- 必须是
machine-applicable、maybe-incorrect、has-placeholders或unspecified中之一
- 必须是
#[subdiagnostic]
#[subdiagnostic]
- 应用于实现了
AddToDiagnostic的类型 (从#[derive(SessionSubdiagnostic)]) - 增加一个子诊断结构体
#[primary_span](可选的)- 应用于
Span字段 - 表示诊断的主要范围 (primary span)
- 应用于
#[skip_arg](可选的)- 应用于任何字段
- 防止该字段作为诊断参数提供
SessionSubdiagnostic
在编译器中,常见的是编写一个函数,在该函数适用的情况下,有条件地将特定的子诊断添加到错误中。
通常,这些子诊断可以使用诊断结构体来表示,即使整个诊断不能使用结构体。
此时,可以使用 SessionSubdiagnostic 派生宏来将部分诊断(例如,注意、标签、帮助或建议)表示为结构体。
示例
考虑如下所示的“expected return type”标签的定义:
#[derive(SessionSubdiagnostic)]
pub enum ExpectedReturnTypeLabel<'tcx> {
#[label(typeck::expected_default_return_type)]
Unit {
#[primary_span]
span: Span,
},
#[label(typeck::expected_return_type)]
Other {
#[primary_span]
span: Span,
expected: Ty<'tcx>,
},
}
与 SessionDiagnotic 不同,SessionSubdiagnostic 可以应用于结构体或枚举体。
放置在结构类型上的属性都可以放置在枚举体成员上(反之亦然)。
SessionSubdiagnostic 应该有一个属性应用于结构体或者应用于枚举的每个成员上,这些属性有:
#[label(...)]定义一个标签#[note(...)]定义一个注意#[help(...)]定义一个帮助#[suggestion{,_hidden,_short,_verbose}(...)]定义一个建议
以上所有内容都必须提供一个 slug 作为第一个位置参数(rustc_error::fluent::* 中的条目的路径)。
slug 唯一地标识了诊断,也是编译器知道要发出什么错误消息的方式(在编译器的默认 locale 中,或者在用户要求的 locale)。有关如何编写可翻译的错误消息以及如何生成 slug 条目的详细信息,请参阅 translation。
举例来说,expected return type 标签的 Fluent 消息如下所示:
typeck-expected-default-return-type = expected `()` because of default return type
typeck-expected-return-type = expected `{$expected}` because of return type
在(类型为 Span 的)字段上使用 #[primary_span] 属性表示子诊断的主要跨度。
只有标签或建议才需要主跨度,因为它们不能缺少跨度。
所有没有属性标注的类型/成员在 Fluent 消息中都可用作变量使用。如果不需要,可以将字段标注为 #[skip_arg]。
与 SessionDiagnostic 一样,SessionSubdiagnostic 也支持 Option<T> 和 Vec<T> 字段。
对类型/成员使用以下一种属性发出建议:
#[suggestion(message = "...", code = "...", applicability = "...")]#[suggestion_hidden(message = "...", code = "...", applicability = "...")]#[suggestion_short(message = "...", code = "...", applicability = "...")]#[suggestion_verbose(message = "...", code = "...", applicability = "...")]
设置建议时,需要在一个字段上设置 #[primary_span];而建议可以有以下子属性:
message指定消息的 Fluent 属性,默认为.suggestoncode指定建议替换的代码,是一个格式字符串(例如,{field_name}会被替换为结构体的field_name字段的值),而不是一个 Fluent 的标识符applaiblity用来指定属性中的适用性,当字段的类型包含Applicablity时不能使用它。
适用性也可以通过 #[applicability] 属性指定为一个 Applicablity 类型的字段。
最后, SessionSubdiagnostic 派生宏将生成一个 SessionSubdiagnostic 的实现,如下所示:
impl<'tcx> AddToDiagnostic for ExpectedReturnTypeLabel<'tcx> {
fn add_to_diagnostic(self, diag: &mut rustc_errors::Diagnostic) {
use rustc_errors::{Applicability, IntoDiagnosticArg};
match self {
ExpectedReturnTypeLabel::Unit { span } => {
diag.span_label(span, DiagnosticMessage::fluent("typeck-expected-default-return-type"))
}
ExpectedReturnTypeLabel::Other { span, expected } => {
diag.set_arg("expected", expected);
diag.span_label(span, DiagnosticMessage::fluent("typeck-expected-return-type"))
}
}
}
}
一旦定义了子诊断,就可以通过将其传递给诊断上的 subdiagnostic 函数( 示例 1 和 示例 2
),或通过将其赋给诊断结构体标注了 #[subdiagnostic] 的字段这两种方式使用子诊断。
属性
#[derive(SessionSubdiagnostic)] 支持以下属性:
#[label | help | note]
#[label(slug)]、 #[help(slug)] 或 #[note(slug)]:
- 应用于结构体或枚举体成员;结构体/枚举体成员属性互斥
- 强制需要的
- 定义一个表示标签/帮助/注意的类型
- slug (强制需要的)
- 唯一标识诊断,并与其 Fluent 消息相对应
- 通向
rustc_errors::fluent中的条目的路径- 在模块中总是以 Fluent 的资源名称开头,通常是诊断所在 crate 的名字
- 例如
rustc_errors::fluent::typeck::field_already_declared,其中rustc_errors::fluent在属性中隐含,所以只需要typeck::field_already_declared
#[suggeston]
#[suggestion{,_hidden,_short,_verbose}(message = "...", code = "...", applicability = "...")]:
- 应用于结构体或枚举体成员;结构体/枚举体成员属性互斥
- 强制需要的
- 定义一个表示建议的类型
message = "..."(强制需要的)- 值是 suggestion 消息的 Fluent 属性(相对于被
slug指定的 Fluent 消息来说) - 默认是 Fluent 的
suggestion属性
- 值是 suggestion 消息的 Fluent 属性(相对于被
code = "..."(强制需要的)- 值是建议替换的代码的格式字符串
applicability = "..."(可选的)- 与字段上的
#[applicability]互斥 - 值是建议的适用性
- 必须是以下一种字符串:
machine-applicablemaybe-incorrecthas-placeholdersunspecified
- 与字段上的
#[primary_span]
#[primary_span]
- 对 label 和 suggeston 是强制需要的,对其他是可选的
- 应用于
Span字段 - 表明子诊断的主要跨度
#[applicability]
#[applicability]
- 可选的
- 只应用于 suggeston
- 表明建议的适用性
#[skip_arg]
#[skip_arg]
- 可选的
- 应用于任何字段
- 防止该字段作为诊断参数提供