Skip to content

Commit

Permalink
feat(semantic): expose Stats (#5755)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
overlookmotel committed Sep 13, 2024
1 parent b4b460f commit 7fa0cb3
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 24 deletions.
12 changes: 6 additions & 6 deletions crates/oxc_semantic/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
12 changes: 12 additions & 0 deletions crates/oxc_semantic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
83 changes: 65 additions & 18 deletions crates/oxc_semantic/src/stats.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -13,22 +9,67 @@ 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,
pub references: u32,
}

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");
Expand All @@ -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<Option<ScopeId>>) {
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);
}
}

0 comments on commit 7fa0cb3

Please sign in to comment.