診断項目
lint を書いていると、特定の型、トレイト、関数をチェックすることがよくあります。これにより、それらをどのようにチェックするかという疑問が生じます。型は完全な型パスでチェックできます。しかし、これにはパスのハードコーディングが必要であり、一部のエッジケースでは誤分類につながる可能性があります。これに対処するために、rustc は Symbol を介して型を識別するために使用される診断項目を導入しました。
診断項目を見つける
診断項目は、rustc_diagnostic_item 属性を使って rustc/std/core/alloc 内の項目に追加されます。特定の型に対応する項目は、ドキュメントでソースコードを開き、この属性を探すことで見つけられます。テスト中のコンパイルエラーを避けるために、cfg_attr 属性とともに追加されていることが多い点に注意してください。定義は多くの場合、次のようになります。
// これはこの型の診断項目です vvvvvvv
#[cfg_attr(not(test), rustc_diagnostic_item = "Penguin")]
struct Penguin;
診断項目は通常、トレイト、 型、 および独立した関数にのみ追加されます。 関連型またはメソッドをチェックすることが目的の場合は、 その項目の診断項目を使用し、 診断項目の使用を参照してください。
診断項目を追加する
新しい診断項目は、次の 2 つの手順で追加できます。
-
Rust リポジトリ内で対象の項目を見つけます。次に、
rustc_diagnostic_item属性を介して、診断項目を文字列として追加します。これにより、テストの実行中にコンパイルエラーが発生することがあります。これらのエラーは、not(test)条件とともにcfg_attr属性を使用することで回避できます(予防策として、すべてのrustc_diagnostic_item属性に追加しても問題ありません)。最終的には、次のようになります。// これが新しい診断項目になります vvv #[cfg_attr(not(test), rustc_diagnostic_item = "Cat")] struct Cat;診断項目の命名規則については、 命名規則を参照してください。
-
コード内の診断項目は、
rustc_span::symbol::sym内のシンボルを介してアクセスされます。 新しく作成した診断項目を追加するには、 モジュールファイルを開き、 リスト内の正しい位置に名前(この場合はCat)を追加するだけです。
これで、変更内容のプルリクエストを作成できます。:tada:
注: Clippy のような他のプロジェクトで診断項目を使用する場合、 リポジトリが同期されるまでに少し時間がかかることがあります。
命名規則
診断項目にはまだ命名規則がありません。 以下は今後使用されるべきいくつかのガイドラインですが、 既存の名前とは異なる場合があります。
- 型、トレイト、列挙型には UpperCamelCase を使用して名前を付けます
(例:
IteratorおよびHashMap) Writerのように複数回使用される型名については、 より正確な名前を選ぶとよいでしょう。 たとえばモジュール名を追加するなどです (例:IoWriter)- 関連項目には独自の診断項目を付けるべきではなく、 代わりに、それらが由来する型の診断項目を介して間接的にアクセスするべきです。
std::mem::swap()のような自由関数には、 重要な(公開)モジュールを接頭辞として付けたsnake_caseを使用して名前を付けるべきです (例:mem_swapおよびcmp_max)- モジュールには通常、診断項目を付けるべきではありません。 診断項目はパスの使用を避けるために追加されたものであり、 したがってモジュールに使用すると、ほとんどの場合逆効果になる可能性が高いです。
診断項目の使用
rustc では、診断項目は rustc_span::symbol::sym モジュール内の Symbol を介して検索されます。これらは、TyCtxt::get_diagnostic_item() を使用して DefId にマッピングしたり、TyCtxt::is_diagnostic_item() を使用して DefId と一致するかをチェックしたりできます。診断項目から DefId にマッピングする場合、そのメソッドは Option<DefId> を返します。これは、シンボルが診断項目でない場合や、たとえば #[no_std] でコンパイルしているときのように型が登録されていない場合に、None になる可能性があります。
以下のすべての例は DefId とその使用法に基づいています。
例: 型をチェックする
#![allow(unused)]
fn main() {
use rustc_span::symbol::sym;
/// この例では、指定された型(`ty`)が `HashMap` 型を持つかどうかを
/// `TyCtxt::is_diagnostic_item()` を使用してチェックします
fn example_1(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::HashMap, adt.did()),
_ => false,
}
}
}
例: トレイト実装をチェックする
#![allow(unused)]
fn main() {
/// この例では、メソッドから取得した指定の [`DefId`] が、診断項目によって
/// 定義されたトレイト実装の一部であるかどうかをチェックします。
fn is_diag_trait_item(
cx: &LateContext<'_>,
def_id: DefId,
diag_item: Symbol
) -> bool {
if let Some(trait_did) = cx.tcx.trait_of_item(def_id) {
return cx.tcx.is_diagnostic_item(diag_item, trait_did);
}
false
}
}
関連型
診断項目の関連型には、まずトレイトの DefId を取得し、その後で TyCtxt::associated_items() を呼び出すことで間接的にアクセスできます。これは AssocItems オブジェクトを返し、さらにチェックを行うために使用できます。この使用例については、clippy_utils::ty::get_iterator_item_ty() を確認してください。
Clippy での使用
Clippy は可能な場合に診断項目を使用しようとし、いくつかのラッパー関数とユーティリティ関数を開発しています。Clippy で診断項目を使用する際は、そのドキュメントも参照してください。(lint を書くための共通ツール を参照してください。)
関連する issue
これらは、このトピックについて本当に深く掘り下げたい人にとってのみ、おそらく興味深いものです :)
- rust#60966: 診断項目を導入した Rust の PR
- 診断項目へ移行するための Clippy の追跡 issue