Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Diagnostic とサブ診断の構造体

rustc には、診断の作成に使用できる 2 つの診断トレイトがあります: DiagnosticSubdiagnostic です。

単純な診断では、 導出された impl を使用できます。たとえば #[derive(Diagnostic)] です。これらは、追加のサブ診断を追加するかどうかを決定するために多くのロジックを必要としない、単純な診断にのみ適しています。

診断がより複雑または動的な振る舞いを必要とする場合、たとえば条件付きでサブ診断を追加する、レンダリングロジックをカスタマイズする、実行時にメッセージを選択するなどの場合は、対応するトレイト(Diagnostic または Subdiagnostic)を手動で実装する必要があります。 このアプローチはより高い柔軟性を提供し、単純で静的な構造を超える診断に推奨されます。

Diagnostic は異なる言語に翻訳できます。

#[derive(Diagnostic)]

以下に示す “field already declared” 診断の[定義][defn]を考えてみましょう:

#[derive(Diagnostic)]
#[diag("field `{$field_name}` is already declared", code = E0124)]
pub struct FieldAlreadyDeclared {
    pub field_name: Ident,
    #[primary_span]
    #[label("field already declared")]
    pub span: Span,
    #[label("`{$field_name}` first declared here")]
    pub prev_span: Span,
}

Diagnostic は構造体と列挙型に対してのみ導出できます。 構造体では型に置かれる属性は、列挙型では各バリアントに置かれます(またはその逆です)。 各 Diagnostic には、構造体または各列挙型バリアントに適用される 属性 #[diag(...)] が 1 つ必要です。

エラーにエラーコード(例: “E0624”)がある場合は、code サブ属性を使用して指定できます。 code の指定は必須ではありませんが、Diag を使用する診断を Diagnostic を使用するように移植している場合は、元からコードがあったならそれを維持すべきです。

#[diag(..)] は、最初の位置引数としてメッセージを指定しなければなりません。 メッセージは英語で書かれますが、ユーザーが要求したロケールに翻訳される場合があります。 翻訳可能なエラーメッセージがどのように書かれ、どのように生成されるかの詳細については、翻訳ドキュメントを参照してください。

アノテーションのない Diagnostic のすべてのフィールドは、上記の例の field_name のように、Fluent メッセージ内で変数として利用できます。 これが望ましくない場合は、フィールドに #[skip_arg] を付けることができます。

型が Span であるフィールドに #[primary_span] 属性を使用すると、その診断のプライマリ span を示し、そこに診断のメインメッセージが付きます。

診断は単なるプライマリメッセージ以上のものです。多くの場合、ラベル、note、help メッセージ、suggestion が含まれ、それらはすべて Diagnostic に指定することもできます。

#[label]#[help]#[warning]#[note] はすべて、型が Span であるフィールドに適用できます。 これらの属性のいずれかを適用すると、その Span を持つ対応するサブ診断が作成されます。 これらの属性は、診断メッセージを引数として取ります。

Diagnostic derive で使用される場合、他の型には特殊な振る舞いがあります:

  • Option<T> に適用された属性はいずれも、その option が Some(..) の場合にのみ サブ診断を出力します。
  • Vec<T> に適用された属性はいずれも、ベクターの各要素について繰り返されます。

#[help]#[warning]#[note] は構造体自体に適用することもできます。その場合、 サブ診断が Span を持たないことを除いて、フィールドに適用した場合とまったく同じように動作します。 これらの属性は、同じ効果を得るために型 () のフィールドにも適用できます。これを Option 型と組み合わせることで、 任意の #[note]/#[help]/#[warning] サブ診断を表現できます。

suggestion は、4 つのフィールド属性のいずれかを使用して出力できます:

  • #[suggestion("message", code = "...", applicability = "...")]
  • #[suggestion_hidden("message", code = "...", applicability = "...")]
  • #[suggestion_short("message", code = "...", applicability = "...")]
  • #[suggestion_verbose("message", code = "...", applicability = "...")]

suggestion は、Span フィールドまたは (Span, MachineApplicability) フィールドのいずれかに適用しなければなりません。 他のフィールド属性と同様に、ユーザーに表示されるメッセージを指定する必要があります。 code は、置換として提案されるべきコードを指定するもので、フォーマット文字列です(例: {field_name} は、構造体の field_name フィールドの値に置き換えられます)。 applicability は、属性内で適用可能性を指定するために使用できますが、 フィールドの型に Applicability が含まれている場合は使用できません。

最終的に、Diagnostic derive は、次のような Diagnostic の実装を生成します:

impl<'a, G: EmissionGuarantee> Diagnostic<'a> for FieldAlreadyDeclared {
    fn into_diag(self, dcx: &'a DiagCtxt, level: Level) -> Diag<'a, G> {
        let mut diag = Diag::new(dcx, level, "field `{$field_name}` is already declared");
        diag.set_span(self.span);
        diag.span_label(
            self.span,
            "field already declared"
        );
        diag.span_label(
            self.prev_span,
            "`{$field_name}` first declared here"
        );
        diag
    }
}

これで診断を定義できましたが、どのように[使用][use]すればよいのでしょうか? 非常に簡単で、構造体のインスタンスを作成し、それを emit_err(または emit_warning)に渡すだけです:

tcx.dcx().emit_err(FieldAlreadyDeclared {
    field_name: f.ident,
    span: f.span,
    prev_span,
});

#[derive(Diagnostic)] のリファレンス

#[derive(Diagnostic)] は次の属性をサポートしています:

  • #[diag("message", code = "...")]
    • struct または enum variant に適用されます。
    • 必須
    • 診断に関連付けるテキストとエラーコードを定義します。
    • メッセージ(必須
    • code = "..."任意
      • エラーコードを指定します。
  • #[note("message")]任意
    • struct、または型が SpanOption<()>() の struct フィールドに適用されます。
    • note サブ診断を追加します。
    • 値は note のメッセージです。
    • Span フィールドに適用された場合、span 付き note を作成します。
  • #[help("message")]任意
    • struct、または型が SpanOption<()>() の struct フィールドに適用されます。
    • help サブ診断を追加します。
    • 値は help メッセージです。
    • Span フィールドに適用された場合、span 付き help を作成します。
  • #[label("message")]任意
    • Span フィールドに適用されます。
    • label サブ診断を追加します。
    • 値は label のメッセージです。
  • #[warning("message")]任意
    • struct、または型が SpanOption<()>() の struct フィールドに適用されます。
    • warning サブ診断を追加します。
    • 値は warning のメッセージです。
  • #[suggestion{,_hidden,_short,_verbose}("message", code = "...", applicability = "...")]任意
    • (Span, MachineApplicability) または Span フィールドに適用されます。
    • suggestion サブ診断を追加します。
    • メッセージ(必須
      • 値はユーザーに表示される suggestion メッセージです。
      • 翻訳ドキュメントを参照してください。
    • code = "..."/code("...", ...)必須
      • 置換として提案されるコードを示す 1 つまたは複数のフォーマット文字列。 複数の値は、複数の置換候補を意味します。
    • applicability = "..."任意
      • machine-applicablemaybe-incorrecthas-placeholdersunspecified のいずれかでなければならない文字列。
  • #[subdiagnostic]
    • Subdiagnostic#[derive(Subdiagnostic)] から)を実装する型に適用されます。
    • サブ診断 struct によって表されるサブ診断を追加します。
  • #[primary_span]任意
    • _Subdiagnostic 上の Span フィールドに適用されます。
    • 診断の primary span を示します。
  • #[skip_arg]任意
    • 任意のフィールドに適用されます。
    • フィールドが診断引数として提供されるのを防ぎます。

#[derive(Subdiagnostic)]

コンパイラでは、適用可能な場合に特定のサブ診断をエラーへ条件付きで追加する関数を書くことが一般的です。 多くの場合、これらのサブ診断は、診断全体を表せない場合であっても、診断 struct を使用して表現できます。 このような状況では、Subdiagnostic derive を使用して、部分的な診断(例: note、label、help、または suggestion)を struct として表すことができます。

以下に示す「expected return type」label の[定義][subdiag_defn]を考えてみます。

#![allow(unused)]
fn main() {
#[derive(Subdiagnostic)]
pub enum ExpectedReturnTypeLabel<'tcx> {
    #[label("expected `()` because of default return type")]
    Unit {
        #[primary_span]
        span: Span,
    },
    #[label("expected `{$expected}` because of return type")]
    Other {
        #[primary_span]
        span: Span,
        expected: Ty<'tcx>,
    },
}
}

Diagnostic と同様に、Subdiagnostic は struct または enum に対して derive できます。 struct の型に配置された属性は、enum では各 variant に配置されます(またはその逆)。 各 Subdiagnostic には、struct または各 variant に、次のいずれか 1 つの属性を適用する必要があります。

  • label を定義するための #[label(..)]
  • note を定義するための #[note(..)]
  • help を定義するための #[help(..)]
  • warning を定義するための #[warning(..)]
  • suggestion を定義するための #[suggestion{,_hidden,_short,_verbose}(..)]

上記はすべて、最初の位置引数として診断メッセージを提供する必要があります。 翻訳可能なエラーメッセージがどのように生成されるかについて詳しくは、翻訳ドキュメントを参照してください。

フィールド(型が Span)で #[primary_span] 属性を使用すると、 サブ診断の primary span を表します。 primary span が必要なのは label または suggestion のみで、これらは span なしにはできません。

アノテーションを持たない型/variant のすべてのフィールドは、 Fluent メッセージ内で変数として利用できます。 これが望ましくない場合、フィールドには #[skip_arg] を付与できます。

Diagnostic と同様に、SubdiagnosticOption<T> および Vec<T> フィールドをサポートします。

suggestion は、型/variant に次の 4 つの属性のいずれかを使用して出力できます。

  • #[suggestion("...", code = "...", applicability = "...")]
  • #[suggestion_hidden("...", code = "...", applicability = "...")]
  • #[suggestion_short("...", code = "...", applicability = "...")]
  • #[suggestion_verbose("...", code = "...", applicability = "...")]

suggestion には、フィールドに #[primary_span] が設定されている必要があり、次のサブ属性を持つことができます。

  • 最初の位置引数は、ユーザーに表示されるメッセージを指定します。
  • code は置換として提案されるべきコードを指定し、 Fluent 識別子ではなく、フォーマット文字列(例: {field_name} は struct の field_name フィールドの値に置き換えられます)です。
  • applicability は、属性内で applicability を指定するために使用できますが、 フィールドの型に Applicability が含まれる場合は使用できません。

applicability は、#[applicability] 属性を使用して(型が Applicability の)フィールドとして指定することもできます。

最終的に、Subdiagnostic derive は、次のような Subdiagnostic の実装を生成します。

#![allow(unused)]
fn main() {
impl<'tcx> Subdiagnostic for ExpectedReturnTypeLabel<'tcx> {
    fn add_to_diag(self, diag: &mut rustc_errors::Diagnostic) {
        use rustc_errors::{Applicability, IntoDiagArg};
        match self {
            ExpectedReturnTypeLabel::Unit { span } => {
                diag.span_label(span, "expected `()` because of default return type")
            }
            ExpectedReturnTypeLabel::Other { span, expected } => {
                diag.set_arg("expected", expected);
                diag.span_label(span, "expected `{$expected}` because of return type")
            }
        }
    }
}
}

定義後、サブ診断は、診断上の subdiagnostic 関数([example][subdiag_use_1] および [example][subdiag_use_2])へ渡すか、 診断 struct の #[subdiagnostic] アノテーション付きフィールドに割り当てることで使用できます。

引数の共有と分離

サブ診断は、情報をレンダリングする前に、独自の引数(つまり、その構造内の特定のフィールド)を Diag 構造体に追加します。 Diag 構造体はメイン診断からの引数も格納するため、サブ診断はメイン診断からの引数も使用できます。

ただし、#[derive(Subdiagnostic)] を実装することによってサブ診断がメイン診断に追加される場合、 rust-lang/rust#142724 で導入された次のルールが、 引数(つまり、Fluent メッセージで使用される変数)の処理に適用されます。

**サブ診断間の引数の分離**:
サブ診断によって設定された引数は、そのサブ診断のレンダリング中にのみ利用できます。
サブ診断がレンダリングされた後、そのサブ診断が導入したすべての引数はメイン診断から復元されます。
これにより、複数のサブ診断が互いの引数スコープを汚染しないことが保証されます。
たとえば、`Vec<Subdiag>` を使用する場合、同じ引数を反復的に何度も追加します。

**サブ診断とメイン診断間の同一引数のオーバーライド**:
サブ診断が、メイン診断にすでに存在する引数と同じ名前の引数を設定した場合、
両者がまったく同じ値でない限り、実行時にエラーが報告されます。
これには 2 つの利点があります:
- メイン診断の引数がサブ診断の属性に現れることを許可する柔軟性を維持します。
たとえば、サブ診断に属性 `#[suggestion("...", code = "{new_vis}")]` があり、`new_vis` はメイン診断構造体内のフィールドである場合です。
- メイン診断または他のサブ診断で必要とされる引数が、意図せず上書きまたは削除されることを防ぎます。

これらのルールにより、サブ診断によって注入される引数が、そのサブ診断自身のレンダリングに厳密にスコープされることが保証されます。
名前の衝突が存在する場合でも、メイン診断の引数はサブ診断のロジックによって影響を受けません。
さらに、サブ診断は必要に応じて、同じ名前のメイン診断の引数にアクセスできます。

### `#[derive(Subdiagnostic)]` のリファレンス
`#[derive(Subdiagnostic)]` は次の属性をサポートします:

- `#[label("message")]`、`#[help("message")]`、`#[warning("message")]`、または `#[note("message")]`
  - _構造体または列挙型バリアントに適用されます。
    構造体/列挙型バリアント属性とは相互に排他的です。_
  - _必須_
  - ラベル、ヘルプ、または注記を表す型を定義します。
  - メッセージ(_必須_)
    - ユーザーに表示される診断メッセージです。
    - [翻訳ドキュメント](./translation.md)を参照してください。
- `#[suggestion{,_hidden,_short,_verbose}("message", code = "...", applicability = "...")]`
  - _構造体または列挙型バリアントに適用されます。
    構造体/列挙型バリアント属性とは相互に排他的です。_
  - _必須_
  - 提案を表す型を定義します。
  - メッセージ(_必須_)
    - ユーザーに表示される診断メッセージです。
    - [翻訳ドキュメント](./translation.md)を参照してください。
  - `code = "..."`/`code("...", ...)`(_必須_)
    - 置換として提案されるコードを示す 1 つまたは複数のフォーマット文字列です。
      複数の値は、複数の可能な置換を意味します。
  - `applicability = "..."`(_任意_)
    - _フィールド上の `#[applicability]` とは相互に排他的です。_
    - 値は提案の適用可能性です。
    - 次のいずれかでなければならない文字列です:
      - `machine-applicable`
      - `maybe-incorrect`
      - `has-placeholders`
      - `unspecified`
- `#[multipart_suggestion{,_hidden,_short,_verbose}("message", applicability = "...")]`
  - _構造体または列挙型バリアントに適用されます。
    構造体/列挙型バリアント属性とは相互に排他的です。_
  - _必須_
  - 複数部分からなる提案を表す型を定義します。
  - メッセージ(_必須_): `#[suggestion]` を参照してください
  - `applicability = "..."`(_任意_): `#[suggestion]` を参照してください
- `#[primary_span]`(ラベルと提案では _必須_、それ以外では _任意_、複数部分からなる提案には適用不可)
  - _`Span` フィールドに適用されます。_
  - サブ診断のプライマリスパンを示します。
- `#[suggestion_part(code = "...")]`(_必須_、複数部分からなる提案にのみ適用可能)
  - _`Span` フィールドに適用されます。_
  - 複数部分からなる提案の 1 つの部分となるスパンを示します。
  - `code = "..."`(_必須_)
    - 値は、置換として提案されるコードを示すフォーマット文字列です。
- `#[applicability]`(_任意_、(単純および複数部分からなる)提案にのみ適用可能)
  - _`Applicability` フィールドに適用されます。_
  - 提案の適用可能性を示します。
- `#[skip_arg]`(_任意_)
  - _任意のフィールドに適用されます。_
  - フィールドが診断引数として提供されることを防ぎます。

[defn]: https://github.com/rust-lang/rust/blob/6201eabde85db854c1ebb57624be5ec699246b50/compiler/rustc_hir_analysis/src/errors.rs#L68-L77
[use]: https://github.com/rust-lang/rust/blob/f1112099eba41abadb6f921df7edba70affe92c5/compiler/rustc_hir_analysis/src/collect.rs#L823-L827

[subdiag_defn]: https://github.com/rust-lang/rust/blob/f1112099eba41abadb6f921df7edba70affe92c5/compiler/rustc_hir_analysis/src/errors.rs#L221-L234
[subdiag_use_1]: https://github.com/rust-lang/rust/blob/f1112099eba41abadb6f921df7edba70affe92c5/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs#L670-L674
[subdiag_use_2]: https://github.com/rust-lang/rust/blob/f1112099eba41abadb6f921df7edba70affe92c5/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs#L704-L707