린트
이 페이지에서는 린트 등록을 둘러싼 몇 가지 메커니즘과 컴파일러에서 린트를 실행하는 방식을 설명합니다.
LintStore는 모든 것이 그 주위를 도는 핵심 인프라입니다. LintStore는 Session의 일부로 보관되며, Session이 생성된 직후 린트 목록으로 채워집니다.
린트와 린트 패스
컴파일러의 린트 메커니즘은 린트와 린트 패스라는 두 부분으로 구성됩니다. 안타깝게도 기존 문서 중 상당수는 이 둘을 모두 그냥 “린트“라고 부릅니다.
먼저 린트 선언 자체가 있으며, 이름과 기본 린트 수준 및 기타 메타데이터는 여기에서 나옵니다. 린트 선언은 일반적으로 declare_lint! 매크로로 정의되며, 이 매크로는 결국 &rustc_lint_defs::Lint 타입의 static으로 귀결됩니다(다만 모든 매크로가 그렇듯 이 매크로도 새 필드를 추가하기가 다소 번거로우므로 향후 바뀔 수 있습니다).
매크로를 사용하지 않고 직접 선언하는 것은 린트로 금지합니다.
린트 선언은 어떠한 “상태“도 가지지 않으며, 린트의 전역 식별자이자 설명일 뿐입니다. 런타임에 린트가 (린트 이름 기준으로) 두 번 등록되지 않았는지 단언합니다.
린트 패스는 모든 린트의 핵심입니다. 특히 린트와 린트 패스는 일대일 관계가 아닙니다. 어떤 린트는 그것을 내보내는 린트 패스가 하나도 없을 수도 있고, 여러 개일 수도 있으며, 단 하나일 수도 있습니다. 컴파일러는 패스가 특정 린트와 어떤 식으로든 연관되어 있는지 추적하지 않으며, 린트는 다른 작업(예: 타입 검사 등)의 일부로 내보내지는 경우가 많습니다.
등록
고수준 개요
rustc_interface::run_compiler에서 LintStore가 생성되고 모든 린트가 등록됩니다.
린트의 ’출처’는 세 가지입니다.
- 내부 린트: rustc 코드베이스에서만 사용하는 린트
- 내장 린트: 컴파일러에 내장되어 있으며 외부 출처에서 제공하지 않는 린트
rustc_interface::Configregister_lints: 생성 과정에서 컴파일러에 전달되는 린트
린트는 LintStore::register_lint 함수를 통해 등록됩니다. 이 등록은 각 린트마다 단 한 번만 이루어져야 하며, 그렇지 않으면 ICE가 발생합니다.
등록이 완료되면 린트 저장소를 Arc에 넣어 “동결“합니다.
린트 패스는 여러 범주(확장 전, early, late, late 모듈) 중 하나에 따로 등록됩니다. 패스는 클로저, 즉 impl Fn() -> Box<dyn X>로 등록되며, 여기서 dyn X는 early 또는 late 린트 패스 트레이트 객체입니다. 린트 패스를 실행할 때는 클로저를 실행한 다음 린트 패스 메서드를 호출합니다. 린트 패스 메서드는 &mut self를 받으므로 내부적으로 상태를 추적할 수 있습니다.
내부 린트
이들은 컴파일러나 clippy 같은 드라이버에서만 사용하는 린트입니다. 이 린트들은 rustc_lint::internal에서 찾을 수 있습니다.
이러한 린트의 한 예로, 린트 패스가 직접 구현되지 않고 declare_lint_pass! 매크로를 사용하여 구현되었는지 확인하는 검사가 있습니다. 이는 LINT_PASS_IMPL_WITHOUT_MACRO 린트를 통해 수행됩니다.
이러한 린트의 등록은 rustc_lint::new_lint_store 내부에서 새 린트 저장소를 생성할 때 호출되는 rustc_lint::register_internals 함수에서 이루어집니다.
내장 린트
이들은 주로 rustc_lint_defs::builtin과 rustc_lint::builtin 두 곳에 기술되어 있습니다. 대개 전자는 린트 자체의 정의를 제공하고 후자는 린트 패스 정의(및 구현)를 제공하지만, 항상 그런 것은 아닙니다.
내장 린트의 등록은 rustc_lint::register_builtins 함수에서 이루어집니다. 내부 린트와 마찬가지로, 이 역시 rustc_lint::new_lint_store 내부에서 이루어집니다.
드라이버 린트
이들은 드라이버가 rustc_interface::Config의 register_lints 필드(콜백)를 통해 제공하는 린트입니다. 드라이버는 이 필드가 이미 설정되어 있으면, 자신이 추가하는 콜백 안에서 현재 설정된 함수를 호출해야 합니다. 드라이버가 여기에 접근하는 가장 좋은 방법은 Config 구조체에 직접 접근할 수 있게 해 주는 Callbacks::config 함수를 오버라이드하는 것입니다.
컴파일러 린트 패스는 하나의 패스로 결합됩니다
컴파일러 내부에서는 성능상의 이유로 보통 수십 개의 린트 패스를 등록하지 않습니다. 대신 각 종류마다 하나의 린트 패스(예: BuiltinCombinedLateLintModPass)를 두고, 이 패스가 내부적으로 개별 린트 패스를 모두 호출합니다. 이렇게 하면 (대개 비어 있는) 각 트레이트 메서드에 대해 동적 디스패치 대신 정적 디스패치의 이점을 얻을 수 있기 때문입니다.
이상적으로는 이렇게 할 필요가 없어야 합니다. 코드를 이해하기가 더 복잡해지기 때문입니다. 그러나 현재의 타입 소거된 린트 저장소 방식에서는 성능상의 이유로 이렇게 하는 것이 유리합니다.