From 7fa0cb38610f98d02c883eac009c72def2ec1ba8 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Fri, 13 Sep 2024 12:19:54 +0000 Subject: [PATCH] feat(semantic): expose `Stats` (#5755) Expose `Stats` type, and provide a method `Semantic::Stats` to obtain them. Note: If we move counting into parser later, parser can use this same `Stats` type (we'd need to move it into `oxc_parser` or `oxc_ast` crate, but `oxc_semantic` can still re-export it. --- crates/oxc_semantic/src/builder.rs | 12 ++--- crates/oxc_semantic/src/lib.rs | 12 +++++ crates/oxc_semantic/src/stats.rs | 83 +++++++++++++++++++++++------- 3 files changed, 83 insertions(+), 24 deletions(-) diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index a98a798a0e97b..8feaca355ccf6 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -244,12 +244,12 @@ impl<'a> SemanticBuilder<'a> { #[cfg(debug_assertions)] { #[allow(clippy::cast_possible_truncation)] - let actual_stats = Stats { - nodes: self.nodes.len() as u32, - scopes: self.scope.len() as u32, - symbols: self.symbols.len() as u32, - references: self.symbols.references.len() as u32, - }; + let actual_stats = Stats::new( + self.nodes.len() as u32, + self.scope.len() as u32, + self.symbols.len() as u32, + self.symbols.references.len() as u32, + ); Stats::assert_accurate(&actual_stats, &stats); } diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index 72ed0f9b4d844..eb8f356f38b75 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -40,6 +40,7 @@ pub use crate::{ node::{AstNode, AstNodes, NodeId}, reference::{Reference, ReferenceFlags, ReferenceId}, scope::ScopeTree, + stats::Stats, symbol::{IsGlobalReference, SymbolTable}, }; use class::ClassTable; @@ -162,6 +163,17 @@ impl<'a> Semantic<'a> { self.cfg.as_ref() } + /// Get statistics about data held in `Semantic`. + pub fn stats(&self) -> Stats { + #[allow(clippy::cast_possible_truncation)] + Stats::new( + self.nodes.len() as u32, + self.scopes.len() as u32, + self.symbols.len() as u32, + self.symbols.references.len() as u32, + ) + } + pub fn is_unresolved_reference(&self, node_id: NodeId) -> bool { let reference_node = self.nodes.get_node(node_id); let AstKind::IdentifierReference(id) = reference_node.kind() else { diff --git a/crates/oxc_semantic/src/stats.rs b/crates/oxc_semantic/src/stats.rs index e80c1a7836d1c..aebbf7e4c1e67 100644 --- a/crates/oxc_semantic/src/stats.rs +++ b/crates/oxc_semantic/src/stats.rs @@ -1,7 +1,3 @@ -//! Visitor to count nodes, scopes, symbols and references in AST. -//! These counts can be used to pre-allocate sufficient capacity in `AstNodes`, -//! `ScopeTree`, and `SymbolTable` to store info for all these items. - use std::cell::Cell; use oxc_ast::{ @@ -13,8 +9,34 @@ use oxc_ast::{ }; use oxc_syntax::scope::{ScopeFlags, ScopeId}; +/// Statistics about data held in [`Semantic`]. +/// +/// Comprises number of AST nodes, scopes, symbols, and references. +/// +/// These counts can be used to pre-allocate sufficient capacity in `AstNodes`, +/// `ScopeTree`, and `SymbolTable` to store info for all these items. +/// +/// * Obtain `Stats` from an existing [`Semantic`] with [`Semantic::stats`]. +/// * Use [`Stats::count`] to visit AST and obtain accurate counts. +/// +/// # Example +/// ``` +/// use oxc_ast::ast::Program; +/// use oxc_semantic::{Semantic, Stats}; +/// +/// fn print_stats_from_semantic(semantic: &Semantic) { +/// dbg!(semantic.stats()); +/// } +/// +/// fn print_stats_from_ast(program: &Program) { +/// dbg!(Stats::count(program)); +/// } +/// ``` +/// +/// [`Semantic`]: super::Semantic +/// [`Semantic::stats`]: super::Semantic::stats #[derive(Default, Debug)] -pub(crate) struct Stats { +pub struct Stats { pub nodes: u32, pub scopes: u32, pub symbols: u32, @@ -22,13 +44,32 @@ pub(crate) struct Stats { } impl Stats { + /// Create new [`Stats`] from specified counts. + pub fn new(nodes: u32, scopes: u32, symbols: u32, references: u32) -> Self { + Stats { nodes, scopes, symbols, references } + } + + /// Gather [`Stats`] by visiting AST and counting nodes, scopes, symbols, and references. + /// + /// Nodes, scopes and references counts will be exactly accurate. + /// Symbols count may be an over-estimate if there are multiple declarations for a single symbol. + /// e.g. `var x; var x;` will produce a count of 2 symbols, but this is actually only 1 symbol. + /// + /// If semantic analysis has already been run on AST, prefer getting counts with [`Semantic::stats`]. + /// They will be 100% accurate, and very cheap to obtain, whereas this method performs a complete + /// AST traversal. + /// + /// [`Semantic::stats`]: super::Semantic::stats pub fn count(program: &Program) -> Self { - let mut stats = Stats::default(); - stats.visit_program(program); - stats + let mut counter = Counter::default(); + counter.visit_program(program); + counter.stats } - #[cfg_attr(not(debug_assertions), expect(dead_code))] + /// Check that estimated [`Stats`] match actual. + /// + /// # Panics + /// Panics if stats are not accurate. pub fn assert_accurate(actual: &Self, estimated: &Self) { assert_eq!(actual.nodes, estimated.nodes, "nodes count mismatch"); assert_eq!(actual.scopes, estimated.scopes, "scopes count mismatch"); @@ -48,40 +89,46 @@ impl Stats { } } -impl<'a> Visit<'a> for Stats { +#[derive(Default)] +struct Counter { + stats: Stats, +} + +/// Visitor to count nodes, scopes, symbols and references in AST +impl<'a> Visit<'a> for Counter { #[inline] fn enter_node(&mut self, _: AstKind<'a>) { - self.nodes += 1; + self.stats.nodes += 1; } #[inline] fn enter_scope(&mut self, _: ScopeFlags, _: &Cell>) { - self.scopes += 1; + self.stats.scopes += 1; } #[inline] fn visit_binding_identifier(&mut self, _: &BindingIdentifier<'a>) { - self.nodes += 1; - self.symbols += 1; + self.stats.nodes += 1; + self.stats.symbols += 1; } #[inline] fn visit_identifier_reference(&mut self, _: &IdentifierReference<'a>) { - self.nodes += 1; - self.references += 1; + self.stats.nodes += 1; + self.stats.references += 1; } #[inline] fn visit_ts_enum_member_name(&mut self, it: &TSEnumMemberName<'a>) { if !it.is_expression() { - self.symbols += 1; + self.stats.symbols += 1; } walk_ts_enum_member_name(self, it); } #[inline] fn visit_ts_module_declaration_name(&mut self, it: &TSModuleDeclarationName<'a>) { - self.symbols += 1; + self.stats.symbols += 1; walk_ts_module_declaration_name(self, it); } }