진단 항목
린트를 작성할 때는 특정 타입, 트레이트, 함수를 검사하는 경우가 많습니다. 그렇다면 이것들을 어떻게 검사해야 하는지에 대한 의문이 생깁니다. 타입은 전체 타입 경로로 검사할 수 있습니다. 하지만 이 방법은 경로를 하드 코딩해야 하며, 일부 엣지 케이스에서는 잘못 분류될 수 있습니다. 이를 해결하기 위해 rustc는 Symbol을 통해 타입을 식별하는 데 사용하는 진단 항목을 도입했습니다.
진단 항목 찾기
진단 항목은 rustc_diagnostic_item 속성을 사용하여 rustc/std/core/alloc 내부의 항목에 추가됩니다. 특정 타입에 대한 항목은 문서에서 소스 코드를 열어 이 속성을 찾아보면 확인할 수 있습니다. 테스트 중 컴파일 오류를 피하기 위해 이 속성은 cfg_attr 속성과 함께 추가되는 경우가 많다는 점에 유의하십시오. 정의는 보통 다음과 같은 형태입니다:
// This is the diagnostic item for this type vvvvvvv
#[cfg_attr(not(test), rustc_diagnostic_item = "Penguin")]
struct Penguin;
진단 항목은 일반적으로 트레이트, 타입, 독립 함수에만 추가됩니다. 연관 타입이나 메서드를 검사하는 것이 목적이라면, 해당 항목의 진단 항목을 사용하고 진단 항목 사용하기를 참고하십시오.
진단 항목 추가하기
새 진단 항목은 다음 두 단계로 추가할 수 있습니다:
-
Rust 저장소에서 대상 항목을 찾으십시오. 이제
rustc_diagnostic_item속성을 통해 진단 항목을 문자열로 추가하십시오. 이로 인해 테스트를 실행하는 동안 컴파일 오류가 발생하는 경우가 있습니다. 이러한 오류는not(test)조건과 함께cfg_attr속성을 사용하면 피할 수 있습니다(예방 차원에서 모든rustc_diagnostic_item속성에 이렇게 추가해도 괜찮습니다). 최종적으로는 다음과 같은 형태가 되어야 합니다:// This will be the new diagnostic item vvv #[cfg_attr(not(test), rustc_diagnostic_item = "Cat")] struct Cat;진단 항목의 명명 규칙에 대해서는 명명 규칙을 참고하십시오.
-
코드에서 진단 항목은
rustc_span::symbol::sym에 있는 심볼을 통해 접근합니다. 새로 만든 진단 항목을 추가하려면 모듈 파일을 열고 목록의 올바른 위치에 이름(이 경우Cat)을 추가하기만 하면 됩니다.
이제 변경 사항으로 풀 리퀘스트를 생성할 수 있습니다.
참고: 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;
/// This example checks if the given type (`ty`) has the type `HashMap` using
/// `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() {
/// This example checks if a given [`DefId`] from a method is part of a trait
/// implementation defined by a diagnostic item.
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에서 진단 항목을 사용할 때는 해당 문서도 함께 참고하십시오. (린트 작성을 위한 공통 도구를 참고하십시오.)
관련 이슈
이 내용은 아마 이 주제를 정말 깊이 파고들고 싶은 분들에게만 흥미로울 것입니다 :)
- rust#60966: 진단 항목을 도입한 Rust PR
- rust-clippy#5393: 하드코딩된 경로 대신 진단 항목을 사용하도록 전환하기 위한 Clippy의 추적 이슈