Lint
このページでは、lint の登録に関する仕組みの一部と、コンパイラ内で lint をどのように実行するかについて説明します。
LintStore は中心となるインフラストラクチャであり、すべてはこれを中心に動きます。
LintStore は Session の一部として保持され、Session が作成された直後に
lint の一覧で埋められます。
Lint と lint パス
コンパイラ内の lint 仕組みには、lint と lint パスという 2 つの部分があります。 残念ながら、既存のドキュメントの多くでは、これらの両方を単に「lint」と呼んでいます。
まず、lint 宣言そのものがあります。
ここで名前、デフォルトの lint レベル、その他のメタデータが定義されます。
これらは通常、declare_lint! マクロによって定義され、
最終的には型 &rustc_lint_defs::Lint の static になります
(ただし、これは将来的に変わる可能性があります。
すべてのマクロと同様に、このマクロも新しいフィールドを追加するにはやや扱いにくいためです)。
マクロを使用しない直接の宣言に対しては lint を行います。
lint 宣言は「状態」を持ちません。単なるグローバルな識別子であり、 lint の説明にすぎません。 これらが(lint 名によって)二重に登録されていないことを実行時にアサートします。
lint パスは、あらゆる lint の中核です。 特に、lint と lint パスの間には 1 対 1 の関係はありません。 ある lint にはそれを発行する lint パスがまったくない場合もあれば、 多数ある場合も、1 つだけの場合もあります。コンパイラは、あるパスが特定の lint と 何らかの形で関連付けられているかどうかを追跡しません。また、多くの場合、 lint は他の処理(たとえば型チェックなど)の一部として発行されます。
登録
高レベルの概要
rustc_interface::run_compiler では、
LintStore が作成され、
すべての lint が登録されます。
lint には 3 つの「ソース」があります。
- 内部 lint: rustc コードベースでのみ使用される lint
- 組み込み lint: コンパイラに組み込まれており、外部ソースから提供されない lint
rustc_interface::Configregister_lints: 構築時にコンパイラへ渡される lint
lint は LintStore::register_lint 関数を介して登録されます。
これはどの lint についても一度だけ行われるべきであり、そうでない場合は ICE が発生します。
登録が完了すると、lint ストアを Arc に配置することで「凍結」します。
lint パスは、カテゴリのいずれか
(展開前、early、late、late module)に個別に登録されます。
パスはクロージャとして登録されます。
つまり impl Fn() -> Box<dyn X> であり、ここで dyn X は early または late の
lint パストレイトオブジェクトです。
lint パスを実行するときは、このクロージャを実行し、その後 lint パスメソッドを呼び出します。
lint パスメソッドは &mut self を受け取るため、内部で状態を追跡できます。
内部 lint
これらは、コンパイラや clippy のようなドライバーだけが使用する lint です。
rustc_lint::internal にあります。
このような lint の例として、lint パスが手書きではなく
declare_lint_pass! マクロを使って実装されていることを確認するチェックがあります。
これは LINT_PASS_IMPL_WITHOUT_MACRO lint によって実現されています。
これらの lint の登録は rustc_lint::register_internals 関数で行われます。
この関数は、rustc_lint::new_lint_store 内で新しい lint ストアを構築するときに呼び出されます。
組み込み lint
これらは主に 2 つの場所、
rustc_lint_defs::builtin と rustc_lint::builtin で説明されています。
多くの場合、前者は lint 自体の定義を提供し、
後者は lint パスの定義(および実装)を提供しますが、
常にそうであるとは限りません。
組み込み lint の登録は rustc_lint::register_builtins 関数で行われます。
内部 lint の場合と同様に、
これは rustc_lint::new_lint_store の内部で行われます。
ドライバー lint
これらは rustc_interface::Config の
register_lints フィールドを介してドライバーから提供される lint であり、このフィールドはコールバックです。
ドライバーは、それがすでに設定されていることを検出した場合、
追加するコールバック内で現在設定されている関数を呼び出すべきです。
ドライバーがこれにアクセスする最善の方法は、
Callbacks::config 関数をオーバーライドすることです。これにより、Config 構造体へ直接アクセスできます。
コンパイラの lint パスは 1 つのパスにまとめられる
コンパイラ内では、パフォーマンス上の理由から、通常は何十もの
lint パスを登録しません。代わりに、各種類につき 1 つの lint パス
(たとえば BuiltinCombinedModuleLateLintPass)を持ち、それが内部で
個々の lint パスをすべて呼び出します。これは、そうすることで、
各(多くの場合は空の)トレイトメソッドについて動的ディスパッチではなく
静的ディスパッチの利点を得られるためです。
理想的には、これはコードの理解を複雑にするため、行う必要がない方が望ましいです。 しかし、現在の型消去された lint ストアのアプローチでは、 パフォーマンス上の理由からそうすることが有益です。