Análisis del código fuente de Rust: Lint - CombinedLintPass

> 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 LintPassalgunos conceptos e implementaciones de y . Sobre la base de estas estructuras, se proporciona una implementación de Lintverificación . Este documento presenta principalmente la implementación de CombinedLintPassesta estructura y se basa en laCombinedLintPass implementación de una mayor optimización .Lint

Paso de pelusa combinado

LintPassRustc implementa Lintla 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 BuiltinCombinedEarlyLintPassestructura 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 LintPassproporciona 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 BuiltinCombinedEarlyLintPassdefinició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, BuiltinCombinedEarlyLintPassla 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 EarlyLintPasslas check_*funciones que deben implementarse y pasa estas funciones y los $argsparámetros a la siguiente macro. Debido a BuiltinCombinedEarlyLintPassque también es un tipo de pelusa temprana, estas funciones también deben implementarse. Con esta macro, BuiltinCombinedEarlyLintPassla 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 BuiltinCombinedEarlyLintPassel . En esta macro se realiza el siguiente trabajo:

  • Genera BuiltinCombinedEarlyLintPassuna , cuyo atributo es early_lint_passesel identificador del lintpass proporcionado por la macro .
  • Implementación fn new() fn name()y fn get_lints()método. Que new()llama al constructor del early_lint_passeslintpass proporcionado.
  • Llame a la macro expand_combined_early_lint_pass_methodspara implementar su propio check_*método .

Con esta macro, BuiltinCombinedEarlyLintPassla 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 BuiltinCombinedEarlyLintPassmacro expande todos los early_lint_methodsmétodos definidos en . Con esta macro, BuiltinCombinedEarlyLintPassla 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 LintPassde las funciones expandidas check_*. Con esta macro, BuiltinCombinedEarlyLintPassla 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, BuiltinCombinedEarlyLintPassla 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 BuiltinCombinedEarlyLintPassel .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.pelusa

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.llamada relacion

Resumir

Este artículo presenta brevemente la definición e implementación de CombinedLintPassesta 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

{{o.nombre}}
{{m.nombre}}

おすすめ

転載: my.oschina.net/chai2010/blog/5572016