- Autor: Zhang Zheng @ Equipo de desarrollo de KusionStack
- Original: https://mp.weixin.qq.com/s/qV-0nXhkuNjzsRTFceHkwQ
> Resumen: el artículo anterior introdujo algunos conceptos e implementaciones de Lint y LintPass. Sobre la base de estas estructuras, se proporciona una implementación sencilla de la comprobación de Lint. Este documento presenta principalmente la implementación de la estructura CombinedLintPass y optimiza aún más la implementación de Lint basada en CombinedLintPass.
antecedentes
En la pila de tecnología KusionStack , el lenguaje de políticas de configuración de KCL es uno de los componentes importantes. Con el fin de ayudar a los usuarios a escribir mejor el código KCL, también hemos desarrollado algunas herramientas de lenguaje para el lenguaje KCL, y Lint es una de ellas. Las herramientas Lint ayudan a los usuarios a verificar posibles problemas y errores en el código, y también se pueden usar para inspecciones de código automatizadas para garantizar la especificación y la calidad del código del almacén. Debido a que el lenguaje KCL se implementa en Rust, algunas funciones también se aprenden y se hace referencia a ellas desde Rustc. Este artículo es un poco de pensamiento y precipitación en el proceso de aprendizaje de Rustc, y compartiré algunos aquí.
El artículo anteriorLint
introdujo LintPass
algunos conceptos e implementaciones de y . Sobre la base de estas estructuras, se proporciona una implementación de Lint
verificación . Este documento presenta principalmente la implementación de CombinedLintPass
esta estructura y se basa en laCombinedLintPass
implementación de una mayor optimización .Lint
Paso de pelusa combinado
LintPass
Rustc implementa Lint
la lógica específica del registro de herramientas. Y use el modo Visitante para atravesar el AST mientras llama al check_*
método .
impl ast_visit::Visitor for Linter {
fn visit_crate(a: ast:crate){
for lintpass in lintpasses{
lintpass.check_crate(a)
}
walk_crate();
}
fn visit_stmt(a: ast:stmt){
for lintpass in lintpasses{
lintpass.check_stmt(a)
}
walk_stmt();
}
...
}
Sin embargo, Rustc y Clippy proporcionan más de 550 definiciones de Lint. Teniendo en cuenta los factores de rendimiento, obviamente es inapropiado definir una gran cantidad de LintPass, registrar e invocar por separado. Rustc ofrece una mejor solución: dado que puede organizar varios Lints en un LintPass, también puede combinar varios LintPass en un CombinedLintPass.
> Los pases de lint del compilador se combinan en un solo pase > Dentro del compilador, por motivos de rendimiento, normalmente no registramos docenas de pases de lint. En su lugar, tenemos un solo pase de pelusa de cada variedad (por ejemplo, BuiltinCombinedModuleLateLintPass) que llamará internamente a todos los pases de pelusa individuales; esto se debe a que obtenemos los beneficios del envío estático sobre el dinámico para cada uno de los métodos de rasgos (a menudo vacíos). > Idealmente, no tendríamos que hacer esto, puesto que añade a la complejidad de entender el código. Sin embargo, con el enfoque actual de almacenamiento de pelusa con borrado de tipo, es beneficioso hacerlo por motivos de rendimiento.
BuiltinCombinedEarlyLintPass
CombinedLintPass también se divide en categorías tempranas y tardías. Tome como ejemplo el lint inicial de builtin rustc_lint::src::lib.rs
, Rustc define una BuiltinCombinedEarlyLintPass
estructura para estos lintpasses en .
early_lint_passes!(declare_combined_early_pass, [BuiltinCombinedEarlyLintPass]);
Aunque esta definición parece tener una sola línea, se ha ampliado con varias macros y se han agregado 14 LintPass
, y cada una LintPass
proporciona más de 50 check_*
métodos. Estas macros se explican una por una a continuación.
Definición de macro de BuiltinCombinedEarlyLintPass
Early_lint_passes
macro_rules! early_lint_passes {
($macro:path, $args:tt) => {
$macro!(
$args,
[
UnusedParens: UnusedParens,
UnusedBraces: UnusedBraces,
UnusedImportBraces: UnusedImportBraces,
UnsafeCode: UnsafeCode,
AnonymousParameters: AnonymousParameters,
EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
NonCamelCaseTypes: NonCamelCaseTypes,
DeprecatedAttr: DeprecatedAttr::new(),
WhileTrue: WhileTrue,
NonAsciiIdents: NonAsciiIdents,
HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
IncompleteFeatures: IncompleteFeatures,
RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment,
]
);
};
}
La primera es la macro early_lint_passes La función principal de esta macro es definir todos los primeros lintpasses. El lintpass aquí está en pares, el :
izquierdo es el identificador de lintpass y el :
derecho es el constructor de lintpass. Entonces habrá EllipsisInclusiveRangePatterns::default()
y DeprecatedAttr::new()
esta forma. early_lint_passes pasará el lintpass temprano definido junto con el segundo parámetro a la siguiente macro. Con esta macro, la BuiltinCombinedEarlyLintPass
definición anterior se amplía a:
declare_combined_early_pass!([BuiltinCombinedEarlyLintPass], [
UnusedParens: UnusedParens,
UnusedBraces: UnusedBraces,
UnusedImportBraces: UnusedImportBraces,
UnsafeCode: UnsafeCode,
AnonymousParameters: AnonymousParameters,
EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
NonCamelCaseTypes: NonCamelCaseTypes,
DeprecatedAttr: DeprecatedAttr::new(),
WhileTrue: WhileTrue,
NonAsciiIdents: NonAsciiIdents,
HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
IncompleteFeatures: IncompleteFeatures,
RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment,
])
declare_combined_early_pass
macro_rules! declare_combined_early_pass {
([$name:ident], $passes:tt) => (
early_lint_methods!(declare_combined_early_lint_pass, [pub $name, $passes]);
)
}
La macro declare_combined_early_pass recibe el nombre (BuiltinCombinedEarlyLintPass) y pasa de la macro early_lint_passes y continúa pasando a la macro early_lint_methods. Con esta macro, BuiltinCombinedEarlyLintPass
la definición continúa ampliándose a:
early_lint_methods!(declare_combined_early_lint_pass,
[pub BuiltinCombinedEarlyLintPass,
[
UnusedParens: UnusedParens,
UnusedBraces: UnusedBraces,
UnusedImportBraces: UnusedImportBraces,
UnsafeCode: UnsafeCode,
AnonymousParameters: AnonymousParameters,
EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
NonCamelCaseTypes: NonCamelCaseTypes,
DeprecatedAttr: DeprecatedAttr::new(),
WhileTrue: WhileTrue,
NonAsciiIdents: NonAsciiIdents,
HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
IncompleteFeatures: IncompleteFeatures,
RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment,
]
]);
Early_lint_methods
macro_rules! early_lint_methods {
($macro:path, $args:tt) => (
$macro!($args, [
fn check_param(a: &ast::Param);
fn check_ident(a: &ast::Ident);
fn check_crate(a: &ast::Crate);
fn check_crate_post(a: &ast::Crate);
...
]);
)
}
La macro early_lint_methods también se introdujo en el artículo anterior, define EarlyLintPass
las check_*
funciones que deben implementarse y pasa estas funciones y los $args
parámetros a la siguiente macro. Debido a BuiltinCombinedEarlyLintPass
que también es un tipo de pelusa temprana, estas funciones también deben implementarse. Con esta macro, BuiltinCombinedEarlyLintPass
la definición continúa ampliándose a:
declare_combined_early_lint_pass!(
[pub BuiltinCombinedEarlyLintPass,
[
UnusedParens: UnusedParens,
UnusedBraces: UnusedBraces,
UnusedImportBraces: UnusedImportBraces,
UnsafeCode: UnsafeCode,
AnonymousParameters: AnonymousParameters,
EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
NonCamelCaseTypes: NonCamelCaseTypes,
DeprecatedAttr: DeprecatedAttr::new(),
WhileTrue: WhileTrue,
NonAsciiIdents: NonAsciiIdents,
HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
IncompleteFeatures: IncompleteFeatures,
RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment,
]
],
[
fn check_param(a: &ast::Param);
fn check_ident(a: &ast::Ident);
fn check_crate(a: &ast::Crate);
fn check_crate_post(a: &ast::Crate);
...
]
)
declare_combined_early_lint_pass
macro_rules! declare_combined_early_lint_pass {
([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], $methods:tt) => (
#[allow(non_snake_case)]
$v struct $name {
$($passes: $passes,)*
}
impl $name {
$v fn new() -> Self {
Self {
$($passes: $constructor,)*
}
}
$v fn get_lints() -> LintArray {
let mut lints = Vec::new();
$(lints.extend_from_slice(&$passes::get_lints());)*
lints
}
}
impl EarlyLintPass for $name {
expand_combined_early_lint_pass_methods!([$($passes),*], $methods);
}
#[allow(rustc::lint_pass_impl_without_macro)]
impl LintPass for $name {
fn name(&self) -> &'static str {
panic!()
}
}
)
}
La macro declare_combined_early_lint_pass es BuiltinCombinedEarlyLintPass
el . En esta macro se realiza el siguiente trabajo:
- Genera
BuiltinCombinedEarlyLintPass
una , cuyo atributo esearly_lint_passes
el identificador del lintpass proporcionado por la macro . - Implementación
fn new()
fn name()
yfn get_lints()
método. Quenew()
llama al constructor delearly_lint_passes
lintpass proporcionado. - Llame a la macro
expand_combined_early_lint_pass_methods
para implementar su propiocheck_*
método .
Con esta macro, BuiltinCombinedEarlyLintPass
la definición se convierte en:
pub struct BuiltinCombinedEarlyLintPass {
UnusedParens: UnusedParens,
UnusedBraces: UnusedBraces,
UnusedImportBraces: UnusedImportBraces,
UnsafeCode: UnsafeCode,
AnonymousParameters: AnonymousParameters,
EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns,
NonCamelCaseTypes: NonCamelCaseTypes,
DeprecatedAttr: DeprecatedAttr,
WhileTrue: WhileTrue,
NonAsciiIdents: NonAsciiIdents,
HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
IncompleteFeatures: IncompleteFeatures,
RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment,
}
impl BuiltinCombinedEarlyLintPass {
pub fn new() -> Self {
Self {
UnusedParens: UnusedParens,
UnusedBraces: UnusedBraces,
UnusedImportBraces: UnusedImportBraces,
UnsafeCode: UnsafeCode,
AnonymousParameters: AnonymousParameters,
EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
NonCamelCaseTypes: NonCamelCaseTypes,
DeprecatedAttr: DeprecatedAttr::new(),
WhileTrue: WhileTrue,
NonAsciiIdents: NonAsciiIdents,
HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
IncompleteFeatures: IncompleteFeatures,
RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment,
}
}
pub fn get_lints() -> LintArray {
let mut lints = Vec::new();
lints.extend_from_slice(&UnusedParens::get_lints());
lints.extend_from_slice(&UnusedBraces::get_lints());
lints.extend_from_slice(&UnusedImportBraces::get_lints());
lints.extend_from_slice(&UnsafeCode::get_lints());
lints.extend_from_slice(&AnonymousParameters::get_lints());
lints.extend_from_slice(&EllipsisInclusiveRangePatterns::get_lints());
lints.extend_from_slice(&NonCamelCaseTypes::get_lints());
lints.extend_from_slice(&DeprecatedAttr::get_lints());
lints.extend_from_slice(&WhileTrue::get_lints());
lints.extend_from_slice(&NonAsciiIdents::get_lints());
lints.extend_from_slice(&HiddenUnicodeCodepoints::get_lints());
lints.extend_from_slice(&IncompleteFeatures::get_lints());
lints.extend_from_slice(&RedundantSemicolons::get_lints());
lints.extend_from_slice(&UnusedDocComment::get_lints());
lints
}
}
impl EarlyLintPass for BuiltinCombinedEarlyLintPass {
expand_combined_early_lint_pass_methods!([$($passes),*], $methods);
}
#[allow(rustc::lint_pass_impl_without_macro)]
impl LintPass for BuiltinCombinedEarlyLintPass {
fn name(&self) -> &'static str {
panic!()
}
}
expand_combined_early_lint_pass_methods
macro_rules! expand_combined_early_lint_pass_methods {
($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
$(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) {
expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*));
})*
)
}
La BuiltinCombinedEarlyLintPass
macro expande todos los early_lint_methods
métodos definidos en . Con esta macro, BuiltinCombinedEarlyLintPass
la definición se convierte en (se omiten otras definiciones):
impl EarlyLintPass for BuiltinCombinedEarlyLintPass {
fn check_param(&mut self, context: &EarlyContext<'_>, a: &ast::Param) {
expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*));
}
fn check_ident(&mut self, context: &EarlyContext<'_>, a: &ast::Ident) {
expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*));
}
fn check_crate(&mut self, context: &EarlyContext<'_>, a: &ast::Crate) {
expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*));
}
...
}
expand_combined_early_lint_pass_method
macro_rules! expand_combined_early_lint_pass_method {
([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({
$($self.$passes.$name $params;)*
})
}
check_*
expand_combined_early_lint_pass_method: llama a cada una LintPass
de las funciones expandidas check_*
. Con esta macro, BuiltinCombinedEarlyLintPass
la definición se convierte en (se omiten otras definiciones):
impl EarlyLintPass for BuiltinCombinedEarlyLintPass {
fn check_param(&mut self, context: &EarlyContext<'_>, a: &ast::Param) {
self.UnusedParens.check_param(context, a);
self.UnusedBraces.check_param(context, a);
self.UnusedImportBraces.check_param(context, a);
...
}
fn check_ident(&mut self, context: &EarlyContext<'_>, a: &ast::Ident) {
self.UnusedParens.check_ident(context, a);
self.UnusedBraces.check_ident(context, a);
self.UnusedImportBraces.check_ident(context, a);
...
}
fn check_crate(&mut self, context: &EarlyContext<'_>, a: &ast::Crate) {
self.UnusedParens.check_crate(context, a);
self.UnusedBraces.check_crate(context, a);
self.UnusedImportBraces.check_crate(context, a);
...
}
...
}
Definición final de BuiltinCombinedEarlyLintPass
A través de la expansión de la macro anterior, BuiltinCombinedEarlyLintPass
la definición en realidad tiene la siguiente forma:
pub struct BuiltinCombinedEarlyLintPass {
UnusedParens: UnusedParens,
UnusedBraces: UnusedBraces,
...
}
impl BuiltinCombinedEarlyLintPass{
pub fn new() -> Self {
UnusedParens: UnusedParens,
UnusedBraces: UnusedBraces,
...
}
pub fn get_lints() -> LintArray {
let mut lints = Vec::new();
lints.extend_from_slice(&UnusedParens::get_lints());
lints.extend_from_slice(&UnusedBraces::get_lints());
...
lints
}
}
impl EarlyLintPass for BuiltinCombinedEarlyLintPass {
fn check_crates(&mut self, context: &EarlyContext<'_>, a: &ast::Crate){
self.UnusedParens.check_crates (context, a);
self.UnusedBraces.check_crates (context, a);
...
}
fn check_ident(&mut self, context: &EarlyContext<'_>, a: Ident){
self.UnusedParens.check_ident (context, a);
self.UnusedBraces.check_ident (context, a);
...
}
..
}
Con esta definición, se pueden implementar múltiples comprobaciones de lintpass utilizando BuiltinCombinedEarlyLintPass
el .check_*
Optimización adicional de Lint
Basado en CombinedLintPass, el diseño de Linter propuesto en el artículo anterior se puede optimizar aún más.
Aquí, el método CombinedLintPass check_*
se puede utilizar para realizar las comprobaciones correspondientes cuando el Visitante atraviesa el AST. Aunque el efecto es el mismo que antes, debido a la relación de las macros, todos los check_*
métodos y lintpasses que deben ejecutarse se recopilan en una estructura, que también es más fácil de administrar. Del mismo modo, debido a que CombinedLintPass realmente llama al método de verificación respectivo de cada lintpass, aunque la llamada puede ser tan complicada como la siguiente figura, pero debido a que la mayoría de los métodos de verificación definidos en lintpass son verificaciones nulas generadas por macros, no causarán pérdida de rendimiento.
Resumir
Este artículo presenta brevemente la definición e implementación de CombinedLintPass
esta y optimiza aún más el diseño de Linter. Espero que pueda ser útil para comprender Rustc y Lint. Si hay algún error, corríjame. Los artículos posteriores continuarán presentando el proceso de registro y ejecución de Lint en Rustc durante el proceso de compilación, y esperamos seguir prestando atención.
Link de referencia
- Anatomía del código fuente de Rust (libro electrónico): https://github.com/awesome-kusion/rust-code-book
- Idioma de la política de configuración de KCL: https://github.com/KusionStack/KCLVM
- KusionStack: https://github.com/KusionStack
- Moho: https://github.com/rust-lang/rust
- Guía de rustc-dev: https://rustc-dev-guide.rust-lang.org/
- Visitante de óxido: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/index.html
- Rust Clippy: https://github.com/rust-lang/rust-clippy