Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(semantic): expose Stats #5755

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}