エラーとlint
rustc が優れたエラーメッセージを持つように、多くの労力が注がれてきました。
この章では、コンパイラからコンパイルエラーとlintを発行する方法について説明します。
診断の構造
診断エラーの主な部分は次のとおりです。
error[E0000]: main error message
--> file.rs:LL:CC
|
LL | <code>
| -^^^^- secondary label
| |
| primary label
|
= note: note without a `Span`, created with `.note`
note: sub-diagnostic message for `.span_note`
--> file.rs:LL:CC
|
LL | more code
| ^^^^
- レベル(
error、warningなど)。メッセージの重大度を示します。 (診断レベルを参照) - コード(たとえば「mismatched types」の場合は
E0308)。 これは、エラーコードインデックスにある問題の拡張説明を通じて、ユーザーが現在のエラーについてさらに情報を得るのに役立ちます。 すべての診断にコードがあるわけではありません。 たとえば、lintによって作成された診断にはコードがありません。 - メッセージ。 問題の主な説明です。 単独でも意味が通じるように、一般的で、それ自体で完結しているべきです。
- 診断ウィンドウ。
これにはいくつかのものが含まれます。
- プライマリspanの開始位置のパス、行番号、列。
- 影響を受けるユーザーのコードとその周辺。
- ユーザーのコードの下に表示されるプライマリspanとセカンダリspan。
これらのspanには、必要に応じて1つ以上のラベルを含めることができます。
- プライマリspanには、問題を説明するのに十分なテキストがあるべきです。 それだけが表示される場合(たとえばIDE内)でも意味が通じるようにするためです。 これは「空間認識的」(コードを指し示す)なので、通常はエラーメッセージよりも簡潔にできます。
- 複数のspanラベルが重なった場合に出力が煩雑になることが予想されるなら、出力を適切に調整するのがよい考えです。
たとえば、
if/else arms have incompatible typesエラーは、アームがすべて同じ行にあるかどうか、いずれかのアームが空かどうか、またそれらのどのケースにも該当しないかに応じて、異なるspanを使用します。
- サブ診断。 どのエラーも、エラーの主要部分に似た複数のサブ診断を持つことができます。 これらは、説明の順序がコードの順序と対応しない場合に使用されます。 説明の順序が「順不同」でよい場合は、通常そのほうが冗長でないため、メイン診断でセカンダリラベルを活用することが推奨されます。
テキストは事実を淡々と述べるものにし、複数の文が_必要_でない限り、大文字化やピリオドは避けるべきです。
error: the fobrulator needs to be krontrificated
メッセージやラベルにコードまたは識別子を表示する必要がある場合は、バッククォートで囲むべきです。
error: the identifier `foo.bar` is invalid
エラーコードと説明
ほとんどのエラーには、関連付けられたエラーコードがあります。
エラーコードは、エラーを発生させる方法の例と、そのエラーについての詳細な情報を含む長文の説明にリンクされています。
これらは --explain フラグ、またはエラーインデックスから確認できます。
一般的なルールとして、説明がエラー自体よりも多くの情報を提供するなら、そのエラーにコード(関連付けられた説明付き)を与えてください。 多くの場合、発行されるエラー自体にすべての情報を入れるほうが適切です。 しかし、そうするとエラーが冗長になってしまう場合や、発生条件が多すぎてすべてのケースに役立つ情報をエラーに含められない場合があります。そのような場合は、説明を追加するのがよい考えです。1 いつものように、確信が持てない場合はレビュー担当者に聞いてください!
関連付けられたエラーコードを持つ新しいエラーを追加することに決めた場合は、そのプロセスに関するガイドと重要な詳細について、このセクションを読んでください。
lintと固定診断
一部のメッセージは、ユーザーがレベルを制御できるlintsを介して発行されます。 ほとんどの診断はハードコードされており、ユーザーはそのレベルを制御できません。
通常、診断を「固定」にすべきかlintにすべきかは明白ですが、いくつか曖昧な領域もあります。
いくつか例を示します。
- 借用チェッカーのエラー: これらは固定エラーです。 ユーザーは、借用チェッカーを黙らせるためにこれらの診断のレベルを調整することはできません。
- デッドコード: これはlintです。 ユーザーはおそらく自分のクレート内にデッドコードを望まないでしょうが、これをハードエラーにすると、リファクタリングや開発が非常に苦痛になります。
- これらを固定エラーにするとあまりにも多くの破壊的影響を引き起こすと判断されたため、代わりに警告が発行されます。 そして最終的には固定(ハード)エラーに変更されます。
ハードコードされた警告(span_warn のようなメソッドを使用するもの)は、通常のコードでは避け、代わりにlintを使用することを推奨します。
CLIフラグに関する警告など、一部の場合にはハードコードされた警告を使用する必要があります。
固定エラーの代わりにエラーレベルのlintをいつ使用するかの指針については、以下の deny lintレベルを参照してください。
診断出力スタイルガイド
- 平易で簡潔な英語で書いてください。 メッセージが、しばらく掃除されていない可能性のある小さな画面に表示されたとき、 一晩パーティーをしてベッドから出てきたばかりの普通のプログラマーに理解できないなら、 それは複雑すぎます。
Error、Warning、Note、およびHelpメッセージは小文字で始め、 句読点で終わらないようにします。- エラーメッセージは簡潔にするべきです。
ユーザーはこれらのエラーメッセージを何度も目にし、
より詳細な説明は
--explainフラグで確認できます。 とはいえ、理解しにくいほど簡潔にしすぎないでください。 - “illegal” という語は無効です。 代わりに “invalid” またはより具体的な語を使ってください。
- エラーは、それが発生したコードの範囲を記録するべきです(これを簡単に行うには
rustc_errors::DiagCtxtのspan_*メソッド、または diagnostic struct の#[primary_span]を使用します)。 また、その範囲が大きすぎない場合は、エラーの原因となった他の範囲もnoteしてください。 - 範囲付きのメッセージを出力するときは、問題を示すのに十分な範囲のうち、 可能な限り最小の範囲に縮小するようにしてください
- 同じエラーに対して複数のエラーメッセージを出力しないようにしてください。 これには重複の検出が必要になる場合があります。
- コンパイラーが特定のエラーメッセージに必要な情報を十分に持っていない場合は、
ライブラリコードに新しい属性を追加し、より多くの情報を追加できるようにするため、
コンパイラーチームに相談してください。
たとえば、
#[rustc_on_unimplemented]を参照してください。 これらのアノテーションが利用可能な場合は使用してください! - Rust の学習曲線はかなり急であり、 コンパイラーメッセージは重要な学習ツールであることを念頭に置いてください。
- コンパイラーについて話すときは、
Rustやrustcではなく、the compilerと呼んでください。 - 項目のリストを書くときは、Oxford comma を使用してください。
Lint の命名
RFC 0344 によると、lint 名は以下のガイドラインに従って一貫しているべきです。
基本ルールは次のとおりです。lint 名は、“allow
lint-name” または “allow lint-name items” として読んだときに意味が通るべきです。
たとえば、“allow deprecated items” と “allow dead_code” は意味が通りますが、“allow
unsafe_block” は文法的に正しくありません(複数形であるべきです)。
-
Lint 名は、チェック対象の悪いものを表すべきです。たとえば
deprecatedのようにして、#[allow(deprecated)](items) が正しく読めるようにします。 したがって、ctypesは適切な名前ではなく、improper_ctypesが適切です。 -
任意の項目に適用される lint(安定性 lint など)は、 何をチェックするかだけを述べるべきです。
deprecated_itemsではなくdeprecatedを使用してください。 これにより lint 名を短く保てます。 (繰り返しますが、“allow lint-name items” と考えてください。) -
lint が特定の文法上の分類に適用される場合は、その分類に言及し、 複数形を使用してください。
unused_variableではなくunused_variablesを使用します。 これにより、#[allow(unused_variables)]が正しく読めるようになります。 -
コードの不要、未使用、または無用な側面を検出する lint には、
unusedという用語を使用するべきです。例:unused_imports、unused_typecasts。 -
関数名に対して行うのと同じ方法で snake case を使用してください。
Diagnostic レベル
さまざまな diagnostic レベルのガイドライン:
-
error: プログラムが無効である、またはプログラマーが特定のwarningをエラーにすると決めたために、 コンパイラーがプログラムをコンパイルできなくなる問題を検出したときに出力されます。 -
warning: コンパイラーがプログラムについて何かおかしな点を検出したときに出力されます。 warning fatigue を避けるため、warning を追加するときは注意するべきであり、 コードに実際には問題がない false-positive を避けるべきです。 warning を発行するのが適切な場合の例をいくつか示します。- deprecated な項目を置き換える、または
Resultを使用するなど、ユーザーが対応を取るべき状況で、 それ以外の点ではコンパイルを妨げない場合。 - コードの意味論に影響を与えずに削除できる不要な構文。
たとえば、未使用コードや不要な
unsafe。 - 正しくない、危険、または紛らわしい可能性が非常に高いものの、
言語としては技術的に許可しており、エラーにする準備や確信がまだ十分ではないコード。
例として、
unused_comparisons(範囲外の比較)やbindings_with_variant_name(ユーザーはおそらくパターン内で束縛を作成するつもりではなかった)があります。 - Future-incompatible lint。過去に何かが偶然または誤って受け入れられていたが、 拒否するとエコシステムに過剰な破壊的変更を引き起こす場合です。
- スタイル上の選択。
たとえば、camel case または snake case、あるいは 2018 edition における
dyntrait warning です。 これらを追加する基準は高く、例外的な状況でのみ使用するべきです。 その他のスタイル上の選択は、 デフォルトで allow の lint にするか、Clippy や rustfmt のような他のツールの一部にするべきです。
- deprecated な項目を置き換える、または
-
help:errorまたはwarningの後に出力され、問題の解決方法についてユーザーに追加情報を提供します。 これらのメッセージには、ツールによる自動的なソース修正を導くために、 suggestion 文字列とrustc_errors::Applicability信頼度レベルが含まれることがよくあります。 詳細については、Suggestions セクションを参照してください。error または warning の部分では、問題の修正方法を提案するべきではありません。 “help” sub-diagnostic のみが提案するべきです。
-
note: より多くのコンテキストを提供し、 warning または error の原因となった追加の状況やコードの箇所を特定するために出力されます。 たとえば、borrow checker は以前の競合する borrow を note します。helpとnote:helpは、問題を修正するためにユーザーが加えられる可能性のある変更を示すために使用するべきです。noteは、それ以外のすべてに使用するべきです。 たとえば、他のコンテキスト、情報や事実、読むべきオンラインリソースなどです。
lint レベルと混同しないでください。lint レベルのガイドラインは次のとおりです。
-
forbid: Lint のデフォルトをforbidにするべきではありません。 -
deny:errordiagnostic レベルと同等です。 いくつかの例:- warning レベルから昇格した future-incompatible または edition ベースの lint。
- 正しくないという信頼度が非常に高いが、 それでも通過を許可するための逃げ道を残したいもの。
-
warn:warningdiagnostic レベルと同等です。 ガイドラインについては上記のwarningを参照してください。 -
allow: デフォルトをallowにするべき lint の種類の例:- lint の false positive 率が高すぎる。
- lint が独断的すぎる。
- lint が実験的である。
- lint が、通常は強制されないものを強制するために使用される。
たとえば、
unsafe_codelint は unsafe code の使用を防ぐために使用できます。
lint レベルに関する詳細情報は、rustc book と reference にあります。
役立つヒントとオプション
エラーの発生源を見つける
特定のエラーがどこで発行されているかを見つける主な方法は3つあります:
-
エラーメッセージ/ラベルの一部、またはエラーコードを
grepします。 これは通常うまく機能し、単純明快ですが、比較的深いコールスタックの背後で、 エラーを発行するコードがエラーを構築するコードから離れている場合があります。 それでも、状況を把握するための良い方法です。 -
nightly 専用フラグ
-Z treat-err-as-bug=1を指定してrustcを呼び出すと、 最初に発行されたエラーが内部コンパイラエラーとして扱われ、エラーが発行された地点の スタックトレースを取得できます。 それより後のエラーでトリガーしたい場合は、1を別の値に変更してください。この方法には制限があります:
- コンパイル済みの
rustcでインライン化されるため、一部の呼び出しはスタックトレースから省かれます。 - エラーの_構築_が、それが_発行_される場所から大きく離れています。
これは
grepの方法で直面した問題と似ています。 場合によっては、複数のエラーを順番に発行するためにバッファリングします。
- コンパイル済みの
-
-Z track-diagnosticsを指定してrustcを呼び出すと、エラーとともにエラーの作成場所が出力されます。
通常の開発プラクティスが適用されます。つまり、物事がどの順序で起きているかを把握するために、
debug!() 文を慎重に使用したり、デバッガーを使用してブレークポイントをトリガーしたりします。
Span
Span は、コンパイル対象のコード内の位置を表すために rustc で使用される主要なデータ構造です。
Span は HIR と MIR のほとんどの構成要素に付加され、より情報量の多いエラーレポートを可能にします。
Span は SourceMap で検索して、span_to_snippet や SourceMap 上の
その他の同様のメソッドでエラーを表示する際に有用な「スニペット」を取得できます。
エラーメッセージ
rustc_errors クレートは、エラーの報告に使用されるユーティリティの大部分を定義しています。
診断は、Diagnostic トレイトを実装する型として実装できます。
これは、診断を発行するロジックとメインのコードパスの分離を強制するため、
新しい診断では推奨されます。
あまり複雑でない診断については、Diagnostic トレイトを derive できます。
Diagnostic structs を参照してください。
トレイト実装内では、以下で説明する API を通常どおり使用できます。
DiagCtxt には、エラーを作成して発行するメソッドがあります。
これらのメソッドは通常、span_err、struct_span_err、span_warn などの名前を持ちます。
多数のメソッドがあり、警告、エラー、致命的エラー、提案など、さまざまな種類の「エラー」を発行します。
一般に、このようなメソッドには2つのクラスがあります。エラーを直接発行するものと、
何を発行するかをより細かく制御できるものです。
たとえば、span_err は、指定された Span で指定されたエラーメッセージを発行しますが、
struct_span_err は代わりに Diag を返します。
これらのメソッドのほとんどは文字列を受け取りますが、新しい診断では、翻訳可能な診断用の型付き識別子を 使用することが推奨されます(Translation を参照)。
Diag では、emit メソッドを呼び出してエラーを発行する前に、
関連する注記や提案をエラーに追加できます。
(Diag を発行するか cancel するかのいずれも行わなかった場合、ICE が発生します。)
何ができるかの詳細については、docs を参照してください。
// `Diag` を取得します。これはまだエラーを発行しません。
let mut err = sess.dcx.struct_span_err(sp, fluent::example::example_error);
// 場合によっては、マクロ生成コードに関する奇妙なエラーの出力を避けるため、
// `sp` がマクロによって生成されたものかどうかを確認する必要があります。
if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
// スニペットを使用して修正案を生成します
err.span_suggestion(suggestion_sp, fluent::example::try_qux_suggestion, format!("qux {}", snippet));
} else {
// スニペットを生成できなかった場合は、具体的な「提案」の代わりに
// 「help」メッセージを発行します。実際には、ここに到達する可能性は低いです。
err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}
// エラーを発行します
err.emit();
example-example-error = oh no! this is an error!
.try-qux-suggestion = try using a qux here
.qux-suggestion = you could use a qux here instead
提案
ユーザーに、そのコードが正確に_なぜ_間違っているのかを伝えるだけでなく、
多くの場合、それをどのように修正すればよいかを伝えることも可能です。
この目的のために、Diag は構造化された提案 API を提供しており、端末上でコードの提案を見やすく整形したり、
--error-format json フラグが渡された場合には rustfix のようなツールが利用できる JSON として出力したりします。
すべての提案が機械的に適用されるべきとは限らず、提案されたコードに対する信頼度には、
高いもの(Applicability::MachineApplicable)から低いもの(Applicability::MaybeIncorrect)まであります。
レベルを選択するときは保守的にしてください。
提案を行うには、Diag の span_suggestion メソッドを使用します。
最後の引数は、その提案が機械的に適用可能かどうかをツールに示すヒントを提供します。
提案は、現在の内容を置き換える対応するコードとともに、1つ以上の span を指します。
提案に付随するメッセージは、次のコンテキストで理解可能でなければなりません:
- 独立したサブ診断として表示される(これがデフォルトの出力です)
- 影響を受ける span を指すラベルとして表示される(冗長性に関するいくつかのヒューリスティックが満たされた場合に自動的に行われます)
- 内容のない
helpサブ診断として表示される(提案がテキストから明らかであるものの、ツールがそれらを適用できるようにしたい場合に使用されます) - 表示されない(_非常に_明らかな場合に使用されますが、それでもツールがそれらを適用できるようにしたい場合です)
たとえば、qux の提案を機械的に適用可能にするには、次のようにします:
let mut err = sess.dcx.struct_span_err(sp, fluent::example::message);
if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
err.span_suggestion(
suggestion_sp,
fluent::example::try_qux_suggestion,
format!("qux {}", snippet),
Applicability::MachineApplicable,
);
} else {
err.span_help(suggestion_sp, fluent::example::qux_suggestion);
}
err.emit();
これは次のようなエラーを出力することがあります
$ rustc mycode.rs
error[E0999]: oh no! this is an error!
--> mycode.rs:3:5
|
3 | sad()
| ^ help: try using a qux here: `qux sad()`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0999`.
提案が複数行にまたがる場合や、提案が複数ある場合など、場合によっては 提案は独立して表示されます:
error[E0999]: oh no! this is an error!
--> mycode.rs:3:5
|
3 | sad()
| ^
help: try using a qux here:
|
3 | qux sad()
| ^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0999`.
Applicability に指定可能な値は次のとおりです:
MachineApplicable: 機械的に適用できます。HasPlaceholders: 提案にプレースホルダーテキストが含まれているため、 機械的に適用できません。 例:try adding a type: `let x: <type>`。MaybeIncorrect: 提案が適切な場合もそうでない場合もあるため、 機械的に適用できません。Unspecified: 上記のどのケースに該当するかわからないため、 機械的に適用できません。
提案スタイルガイド
-
提案は疑問文にするべきではありません。 特に、「〜の意味でしたか」のような表現は避けるべきです。 特定の提案がなぜ行われているのかが明確でない場合があります。 そのような場合は、その提案が何であるかを率直に示すほうが適切です。
「
Fooの意味でしたか」と 「似た名前の構造体があります:Foo」を比較してください。 -
メッセージには、「次の」「示すように」などのフレーズを含めるべきではありません。 何について述べているかは span で伝えてください。
-
メッセージには、「xyz を行うには、使用してください」や「xyz を行うには、abc を使用してください」のような追加の指示を含めてもかまいません。
-
メッセージには関数、変数、型の名前を含めてもかまいませんが、式全体は避けてください。
リント
コンパイラのリント基盤は rustc_middle::lint モジュールで定義されています。
リントはいつ実行されるか?
リントが役割を果たすために必要な情報に応じて、異なるリントは異なるタイミングで実行されます。 一部のリントはパスにグループ化され、そのパス内のリントは 1 つのビジターを通じてまとめて処理されます。 パスには次のようなものがあります:
-
展開前パス: マクロ展開の前の AST ノードに対して動作します。 一般的には避けるべきです。
- 例:
keyword_identsは、将来のエディションでキーワードになる 識別子をチェックしますが、マクロ内で使用される識別子に影響を受けやすいです。
- 例:
-
早期リントパス: マクロ展開と名前解決の後、 AST lowering の直前の AST ノードに対して動作します。 これらのリントは、純粋に構文的なリント用です。
- 例:
unused_parensリントは、if条件のように不要な状況で かっこで囲まれた式をチェックします。
- 例:
-
後期リントパス: 解析の終盤(借用チェックなどの後)の HIR ノードに対して動作します。これらのリントでは完全な型情報を利用できます。 ほとんどのリントは後期リントです。
- 例:
invalid_valueリント(明らかに無効な未初期化値をチェックするリント)は、 型が未初期化のままにできるかどうかを判定するために型情報を必要とするため、 後期リントです。
- 例:
-
MIR パス: MIR ノードに対して動作します。 これは他のパスとまったく同じではありません。 MIR ノードに対して動作するリントには、実行のための独自のメソッドがあります。
- 例:
arithmetic_overflowリントは、オーバーフローする可能性のある 定数値を検出したときに生成されます。
- 例:
ほとんどのリントはパスシステムを通じてうまく機能し、かなり単純なインターフェイスと
簡単な統合方法(たいていは特定の check 関数を実装するだけ)を備えています。
しかし、一部のリントは
コンパイラ内の特定のコードパス上に置くほうが書きやすい場合があります。
たとえば、unused_mut リントは、借用チェッカー内のいくつかの
情報と状態を必要とするため、借用チェッカー内で実装されています。
これらのインラインリントの一部は、リントシステムの準備が整う前に発火します。 それらのリントはバッファリングされ、リントシステムの準備が整う コンパイラの後続フェーズまで保持されます。 コンパイラの初期段階でのリントを参照してください。
リント定義用語
リントは LintStore を介して管理され、さまざまな方法で登録されます。
次の用語は、一般に登録方法に基づいた
リントのさまざまなクラスを指します。
- 組み込み リントはコンパイラソース内で定義されます。
- ドライバー登録 リントは、外部ドライバーによってコンパイラドライバーが作成されるときに 登録されます。 これは、たとえば Clippy が使用する仕組みです。
- ツール リントは、
clippy::やrustdoc::のようなパス接頭辞を持つリントです。 - 内部 リントは、rustc ソースツリー自体でのみ実行される
rustc::スコープのツールリントであり、通常の組み込みリントと同様にコンパイラソースで定義されます。
リント登録の詳細は、LintStore の章を参照してください。
リントを宣言する
組み込みのコンパイラリントは rustc_lint クレートで定義されています。
他のクレートで実装する必要があるリントは rustc_lint_defs で定義されます。
可能であれば、リントは rustc_lint に配置することを優先すべきです。
利点の 1 つは、依存関係のルートに近いため、作業がはるかに高速になる可能性があることです。
各リントは、LintPass trait を実装する struct によって実装されます
(リントを実行するのに最適なタイミングに応じて、より具体的なリントパスのトレイトである
EarlyLintPass または LateLintPass のいずれかを実装することもできます)。
このトレイト実装により、リンターが AST を走査するときに、特定の構文構造をチェックできます。
その後、コンパイルエラーと非常によく似た方法でリントを出力できます。
また、declare_lint! マクロを使用して、特定のリントのメタデータも宣言します。
このマクロには、名前、デフォルトレベル、短い説明、およびさらにいくつかの詳細が含まれます。
リントとリントパスはコンパイラに登録する必要があることに注意してください。
たとえば、次のリントは
while true { ... } の使用をチェックし、代わりに loop { ... } を使用することを提案します。
// `WHILE_TRUE` という名前のリントを宣言する
declare_lint! {
WHILE_TRUE,
// デフォルトで warn
Warn,
// この文字列はリントの説明
"`while true { }` の代わりに `loop { }` の使用を提案する"
}
// これは、関連付けられたリントのリストを提供する struct とリントパスを宣言します。
// コンパイラは現在、関連付けられたリントを直接使用していません(たとえば、
// パスを実行しないようにしたり、パスが適切なリントのセットを出力するかを
// チェックしたりするためには使用していません)。ただし、ここで正確にしておくのはよいことです。
// なぜなら、リントパス上の get_lints メソッド(このマクロが生成するもの)経由で
// リントを登録するようになる可能性があるためです。
declare_lint_pass!(WhileTrue => [WHILE_TRUE]);
// `WhileTrue` リント用のヘルパー関数。
// 任意の数の括弧をたどり、最初の非括弧式を返す。
fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
while let ast::ExprKind::Paren(sub) = &expr.kind {
expr = sub;
}
expr
}
// `EarlyLintPass` には多くのメソッドがあります。このリントでは必要なのがそれだけなので、
// `check_expr` の定義のみをオーバーライドしていますが、独自のリントでは
// 他のメソッドをオーバーライドできます。メソッドの完全な一覧については
// rustc のドキュメントを参照してください。
impl EarlyLintPass for WhileTrue {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
if let ast::ExprKind::While(cond, ..) = &e.kind
&& let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind
&& let ast::LitKind::Bool(true) = lit.kind
&& !lit.span.from_expansion()
{
let condition_span = cx.sess.source_map().guess_head_span(e.span);
cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
lint.build(fluent::example::use_loop)
.span_suggestion_short(
condition_span,
fluent::example::suggestion,
"loop".to_owned(),
Applicability::MachineApplicable,
)
.emit();
})
}
}
}
example-use-loop = 無限ループを `loop {"{"} ... {"}"}` で表す
.suggestion = `loop` を使用する
エディションでゲートされるリント
新しいエディションでリントの動作を変更したい場合があります。
これを行うには、
declare_lint! の呼び出しに遷移を追加するだけです。
declare_lint! {
pub ANONYMOUS_PARAMETERS,
Allow,
"匿名パラメーターを検出する",
Edition::Edition2018 => Warn,
}
これにより、ANONYMOUS_PARAMETERS リントは 2015 エディションではデフォルトで allow になりますが、
2018 エディションではデフォルトで warn になります。
詳細については、エディション固有のリントを参照してください。
機能でゲートされるリント
ある機能に属するリントは、その機能がクレートで有効になっている場合にのみ使用可能であるべきです。 これをサポートするために、リント宣言には次のように機能ゲートを含めることができます。
declare_lint! {
pub SOME_LINT_NAME,
Warn,
"新しく有用だが、機能でゲートされるリント",
@feature_gate = sym::feature_name;
}
将来非互換リント
コンパイラ内での future-incompatible という用語の使用は、rustc がコンパイラのユーザーに公開しているものよりも少し広い意味を持ちます。
rustc 内部では、将来非互換リントは、ユーザーが書いたコードが将来コンパイルできなくなる可能性があることをユーザーに知らせるためのものです。 一般に、将来非互換コードは次の 2 つの理由で存在します。
- ユーザーが、コンパイラが誤って受け入れた不健全なコードを書いた場合。 健全性の穴を修正することは Rust の後方互換性保証の範囲内ですが (ユーザーのコードは壊れます)、このリントは、コードがどのエディションを使用しているかに関係なく、 今後の rustc のあるバージョンでこれが起こることをユーザーに警告するためにあります。これが、rustc が 「future incompatible」としてユーザーに排他的に公開している意味です。
- ユーザーが、今後のエディションでコンパイルできなくなるまたは意味が変わるコードを書いた場合。
これらはしばしば「エディションリント」と呼ばれ、ユーザーがクレートのエディションを更新した場合に
壊れるコードをリントするために使用される、さまざまな「エディション互換性」リントグループ
(例:
rust_2021_compatibility)で一般的に見られます。 詳細については、移行リントを参照してください。
将来非互換リントは、追加の「フィールド」@future_incompatible を使用して宣言する必要があります。
declare_lint! {
pub ANONYMOUS_PARAMETERS,
Allow,
"匿名パラメーターを検出する",
@future_incompatible = FutureIncompatibleInfo {
reason: fcw!(EditionError 2018 "slug-of-edition-guide-page")
};
}
将来非互換の変更が発生する理由を説明する reason フィールドに注意してください。
これにより、ユーザーが受け取る診断メッセージが変更されるだけでなく、リントがどのリントグループに追加されるかも決定されます。
上記の例では、このリントは「エディションリント」です
(その「reason」が EditionError であるため)。これは、匿名パラメーターの使用が Rust 2018 以降では
コンパイルできなくなることをユーザーに示します。
LintStore::register_lints 内では、future_incompatible フィールドを持つリントは
(その reason がエディションに結び付いている場合)エディションベースのリントグループ、または
future_incompatibility リントグループのいずれかに配置されます。
declare_lint! マクロでサポートされていないオプションの組み合わせが必要な場合は、
いつでも declare_lint! マクロを変更してそれをサポートできます。
リントの名前変更または削除
リントの名前が不適切である、またはもはや不要であると判断された場合、
そのリントは名前変更または削除として登録する必要があります。これにより、ユーザーが古いリント名を使用しようとすると警告が発生します。
名前変更/削除を宣言するには、rustc_lint::register_builtins 関数のコードに
store.register_renamed または store.register_removed を含む行を追加します。
store.register_renamed("single_use_lifetime", "single_use_lifetimes");
リントグループ
リントはグループ単位で有効にできます。
これらのグループは、rustc_lint::lib 内の
register_builtins 関数で宣言されます。
add_lint_group! マクロは新しいグループを宣言するために使用されます。
例:
add_lint_group!(sess,
"nonstandard_style",
NON_CAMEL_CASE_TYPES,
NON_SNAKE_CASE,
NON_UPPER_CASE_GLOBALS);
これは、列挙されたリントを有効にする nonstandard_style グループを定義します。
ユーザーは、ソースコード内の #![warn(nonstandard_style)] 属性を使うか、
コマンドラインで -W nonstandard-style を渡すことで、これらのリントを有効にできます。
一部のリントグループは LintStore::register_lints で自動的に作成されます。
たとえば、
理由が FutureIncompatibilityReason::FutureReleaseError である
FutureIncompatibleInfo とともに宣言された任意のリント
(declare_lint! で @future_incompatible が使われた場合のデフォルト)は、
future_incompatible リントグループに追加されます。
エディションにも独自のリントグループ
(例: rust_2021_compatibility)があり、指定されたエディションで破壊的変更となる
将来互換性のないコードを通知する任意のリントに対して自動的に生成されます。
コンパイラの早い段階でのリント
場合によっては、リントシステムが初期化される前 (例: パース中やマクロ展開中)に実行されるリントを定義する必要があります。 これは、警告、エラー、または何も出力しないかを判断するには、 リントレベルが計算済みである必要があるため問題になります。
この問題を解決するために、リントシステムが処理されるまでリントをバッファリングします。
Session と ParseSess はどちらも、
後で使うためにリントをバッファリングできる buffer_lint メソッドを持っています。
リントシステムは、後でバッファリングされたリントの処理を自動的に引き受けます。
したがって、コンパイルの早い段階で実行されるリントを定義するには、
通常どおりリントを定義しつつ、buffer_lint でそのリントを呼び出します。
コンパイラのさらに早い段階でのリント
パーサー(rustc_ast)は、他のどの rustc* クレートにも依存できないという点で興味深い存在です。
特に、コンパイラのリントインフラストラクチャがすべて定義されている
rustc_middle::lint や rustc_lint に依存できません。
これは厄介です!
これを解決するために、rustc_ast は独自のバッファリングされたリント型を定義しており、ParseSess::buffer_lint はそれを使用します。
マクロ展開後、これらのバッファリングされたリントは、
コンパイラの残りの部分で使用される Session::buffered_lints に投入されます。
JSON 診断出力
コンパイラは、診断を JSON オブジェクトとして出力するための
--error-format json フラグを受け付けます(cargo fix などのツールのため)。
これは次のようになります。
$ rustc json_error_demo.rs --error-format json
{"message":"cannot add `&str` to `{integer}`","code":{"code":"E0277","explanation":"\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"},"level":"error","spans":[{"file_name":"json_error_demo.rs","byte_start":50,"byte_end":51,"line_start":4,"line_end":4,"column_start":7,"column_end":8,"is_primary":true,"text":[{"text":" a + b","highlight_start":7,"highlight_end":8}],"label":"no implementation for `{integer} + &str`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the trait `std::ops::Add<&str>` is not implemented for `{integer}`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error[E0277]: cannot add `&str` to `{integer}`\n --> json_error_demo.rs:4:7\n |\n4 | a + b\n | ^ no implementation for `{integer} + &str`\n |\n = help: the trait `std::ops::Add<&str>` is not implemented for `{integer}`\n\n"}
{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to previous error\n\n"}
{"message":"For more information about this error, try `rustc --explain E0277`.","code":null,"level":"","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0277`.\n"}
出力は一連の行であり、その各行は JSON
オブジェクトですが、残念ながら、一連の行全体をまとめたものは
有効な JSON ではないため、そのようなものを必要とするツールや小技(たとえば
python3 -m json.tool へのパイプ)を妨げます。
(これは LSP のパフォーマンス上の目的で意図的だったのではないかとも推測されます。
各行/オブジェクトをフラッシュされ次第送信できるようにするためでしょうか?)
また、“rendered” フィールドにも注意してください。これは “human” 出力を 文字列として含みます。これは、UI テストが構造化された JSON を利用しつつ、 すべてを 2 回コンパイルしなくても “human” 出力(まあ、色は_なしで_)を 確認できるように導入されました。
“human” 可読形式と JSON 形式エミッターは、どちらも
rustc_errors の下にあります。どちらも rustc_ast クレートから
rustc_errors クレートへ移動されました。
JSON エミッターは、JSON シリアライズ用に
独自の Diagnostic 構造体
(およびサブ構造体)を定義しています。
これを
errors::Diag と混同しないでください!
#[rustc_on_unimplemented]
この属性により、トレイト定義は、実装が 期待されたものの見つからなかった場合にエラーメッセージを変更できます。 属性内の文字列リテラルはフォーマット文字列であり、名前付きパラメーターでフォーマットできます。 許可されるパラメーターについては、後述の「フォーマット」セクションを参照してください。
#[rustc_on_unimplemented(message = "an iterator over \
elements of type `{A}` cannot be built from a \
collection of type `{Self}`")]
trait MyIterator<A> {
fn next(&mut self) -> A;
}
fn iterate_chars<I: MyIterator<char>>(i: I) {
// ...
}
fn main() {
iterate_chars(&[1, 2, 3][..]);
}
ユーザーがこれをコンパイルすると、次のように表示されます。
error[E0277]: an iterator over elements of type `char` cannot be built from a collection of type `&[{integer}]`
--> src/main.rs:13:19
|
13 | iterate_chars(&[1, 2, 3][..]);
| ------------- ^^^^^^^^^^^^^^ the trait `MyIterator<char>` is not implemented for `&[{integer}]`
| |
| required by a bound introduced by this call
|
note: required by a bound in `iterate_chars`
以下の内容を変更できます:
- メインエラーメッセージ (
message) - ラベル (
label) - note(複数可) (
note)
たとえば、次の属性は
#[rustc_on_unimplemented(message = "message", label = "label", note = "note")]
trait MyIterator<A> {
fn next(&mut self) -> A;
}
次の出力を生成します。
error[E0277]: message
--> <file>:10:19
|
10 | iterate_chars(&[1, 2, 3][..]);
| ------------- ^^^^^^^^^^^^^^ label
| |
| required by a bound introduced by this call
|
= help: the trait `MyIterator<char>` is not implemented for `&[{integer}]`
= note: note
note: required by a bound in `iterate_chars`
ここまで説明した機能は、
#[diagnostic::on_unimplemented]
でも利用できます。
可能であれば、代わりにそちらを使用するべきです。
フィルタリング
より対象を絞ったエラーメッセージを可能にするために、これらのフィールドの適用を
on でフィルタリングできます。
以下の boolean フラグでフィルタリングできます:
crate_local: トレイト境界が満たされなくなる原因のコードが、 ユーザーのクレートの一部かどうか。 これは、依存関係の変更を必要とするコード変更を提案しないようにするために使用されます。direct: これが派生したオブリゲーションではなく、ユーザー指定のオブリゲーションかどうか。from_desugaring: たとえば?やtryブロックのような、 何らかのデシュガリングの中にいるかどうか。 このフラグもマッチ対象にできます。以下を参照してください。
name = "value" を使用して、以下の名前と値にマッチできます:
cause:ObligationCauseCodeenum の 1 つのバリアントに対してマッチします。 サポートされているのは"MainFunctionType"のみです。from_desugaring:DesugaringKindenum の特定のバリアントに対してマッチします。 デシュガリングは、そのバリアント名で識別されます。たとえば?デシュガリングなら"QuestionMark"、tryブロックなら"TryBlock"です。Selfと、トレイトの任意のジェネリック引数。たとえばSelf = "alloc::string::String"やRhs="i32"です。
コンパイラーはいくつかのマッチ対象の値を提供できます。たとえば:
- 型引数を解決した場合としない場合の両方でプリティプリントされた self_ty。
- self_ty が型の判明している整数型である場合は
"{integral}"。 - 該当する場合は
"[]"、"[{ty}]"、"[{ty}; _]"、"[{ty}; $N]"。 - それらのスライスおよび配列への参照。
- self が関数である場合は
"fn"、"unsafe fn"、または"#[target_feature] fn"。 - 型が数値だが、まだ推論できていない場合は
"{integer}"と"{float}"。 - self が ADT であることにマッチするための
"{struct}"、"{enum}"、"{union}" "[{integral}; _]"のような、上記の組み合わせ。
たとえば、Iterator トレイトは次のようにフィルタリングできます。
#[rustc_on_unimplemented(
on(Self = "&str", note = "call `.chars()` or `.as_bytes()` on `{Self}`"),
message = "`{Self}` is not an iterator",
label = "`{Self}` is not an iterator",
note = "maybe try calling `.iter()` or a similar method"
)]
pub trait Iterator {}
これにより、以下の出力が生成されます。
error[E0277]: `Foo` is not an iterator
--> src/main.rs:4:16
|
4 | for foo in Foo {}
| ^^^ `Foo` is not an iterator
|
= note: maybe try calling `.iter()` or a similar method
= help: the trait `std::iter::Iterator` is not implemented for `Foo`
= note: required by `std::iter::IntoIterator::into_iter`
error[E0277]: `&str` is not an iterator
--> src/main.rs:5:16
|
5 | for foo in "" {}
| ^^ `&str` is not an iterator
|
= note: call `.chars()` or `.bytes() on `&str`
= help: the trait `std::iter::Iterator` is not implemented for `&str`
= note: required by `std::iter::IntoIterator::into_iter`
on フィルターは、cfg 属性と同様に all、any、not 述語を受け付けます。
#[rustc_on_unimplemented(on(
all(Self = "&str", T = "alloc::string::String"),
note = "you can coerce a `{T}` into a `{Self}` by writing `&*variable`"
))]
pub trait From<T>: Sized {
/* ... */
}
フォーマット
文字列リテラルは、中括弧で囲まれたパラメーターを受け付けるフォーマット文字列ですが、 位置指定パラメーター、列挙されたパラメーター、フォーマット指定子は受け付けられません。 以下のパラメーター名が有効です:
Selfと、トレイトのすべてのジェネリックパラメーター。This: 属性が付いているトレイトの名前。ジェネリクスは含みません。Trait: 「糖衣構文化された」トレイトの名前。TraitRefPrintSugaredを参照してください。ItemContext: 現在いるhir::Nodeの種類。"an async block"、"a function"、"an async function"などです。
たとえば次のようなものです:
#![feature(rustc_attrs)]
#[rustc_on_unimplemented(message = "Self = `{Self}`, \
T = `{T}`, this = `{This}`, trait = `{Trait}`, \
context = `{ItemContext}`")]
pub trait From<T>: Sized {
fn from(x: T) -> Self;
}
fn main() {
let x: i8 = From::from(42_i32);
}
メッセージを次のようにフォーマットします
"Self = `i8`, T = `i32`, this = `From`, trait = `From<i32>`, context = `a function`"