perf(isolated_declarations): key scope maps by Ident (#26380)

The declaration scope tree keyed its `bindings` and `references` maps by `Str`, built from `ident.name.into()`. That throws away the hash `Ident` already carries, so every identifier reference was hashed again on insert, again when resolved against the scope's bindings on scope exit, and once more for each parent scope an unresolved reference propagated to.

Key both maps by `Ident` with `IdentHashMap`, and take `Ident` in the lookup helpers so callers pass the AST name straight through. The only non-AST caller, `create_unique_name`, builds an `Ident` instead of a `Str`.

`isolated-declarations/vue-id.ts` benchmark: 4.38 ms -> 3.85 ms (-12%).

AI-assisted.
This commit is contained in:
Dunqing
2026-09-14 02:34:16 +00:00
parent a242469f37
commit 1f902a6962
4 changed files with 43 additions and 44 deletions
@@ -51,7 +51,7 @@ impl<'a> IsolatedDeclarations<'a> {
) -> Option<VariableDeclarator<'a>> {
if decl.id.is_destructuring_pattern() {
decl.id.bound_names(&mut |id| {
if !check_binding || self.scope.has_value_reference(&id.name) {
if !check_binding || self.scope.has_value_reference(id.name) {
self.error(binding_element_export(id.span));
}
});
@@ -60,7 +60,7 @@ impl<'a> IsolatedDeclarations<'a> {
if check_binding
&& let Some(name) = decl.id.get_identifier_name()
&& !self.scope.has_value_reference(&name)
&& !self.scope.has_value_reference(name)
{
return None;
}
@@ -177,7 +177,7 @@ impl<'a> IsolatedDeclarations<'a> {
match decl {
Declaration::FunctionDeclaration(func) => {
let needs_transform = !check_binding
|| func.id.as_ref().is_some_and(|id| self.scope.has_value_reference(&id.name));
|| func.id.as_ref().is_some_and(|id| self.scope.has_value_reference(id.name));
needs_transform
.then(|| Declaration::FunctionDeclaration(self.transform_function(func, None)))
}
@@ -186,12 +186,12 @@ impl<'a> IsolatedDeclarations<'a> {
.map(Declaration::VariableDeclaration),
Declaration::ClassDeclaration(decl) => {
let needs_transform = !check_binding
|| decl.id.as_ref().is_some_and(|id| self.scope.has_reference(&id.name));
|| decl.id.as_ref().is_some_and(|id| self.scope.has_reference(id.name));
needs_transform
.then(|| Declaration::ClassDeclaration(self.transform_class(decl, None)))
}
Declaration::TSTypeAliasDeclaration(alias_decl) => {
if !check_binding || self.scope.has_reference(&alias_decl.id.name) {
if !check_binding || self.scope.has_reference(alias_decl.id.name) {
let mut decl = decl.clone_in(self.allocator());
self.visit_declaration(&mut decl);
Some(decl)
@@ -200,7 +200,7 @@ impl<'a> IsolatedDeclarations<'a> {
}
}
Declaration::TSInterfaceDeclaration(interface_decl) => {
if !check_binding || self.scope.has_reference(&interface_decl.id.name) {
if !check_binding || self.scope.has_reference(interface_decl.id.name) {
let mut decl = decl.clone_in(self.allocator());
self.visit_declaration(&mut decl);
Some(decl)
@@ -209,7 +209,7 @@ impl<'a> IsolatedDeclarations<'a> {
}
}
Declaration::TSEnumDeclaration(enum_decl) => {
if !check_binding || self.scope.has_reference(&enum_decl.id.name) {
if !check_binding || self.scope.has_reference(enum_decl.id.name) {
Some(self.transform_ts_enum_declaration(enum_decl))
} else {
None
@@ -225,7 +225,7 @@ impl<'a> IsolatedDeclarations<'a> {
}
}
Declaration::TSNamespaceDeclaration(decl) => {
if !check_binding || self.scope.has_reference(&decl.id.name) {
if !check_binding || self.scope.has_reference(decl.id.name) {
Some(Declaration::TSNamespaceDeclaration(
self.transform_ts_namespace_declaration(decl),
))
@@ -237,7 +237,7 @@ impl<'a> IsolatedDeclarations<'a> {
Some(Declaration::TSGlobalDeclaration(decl.clone_in(self.allocator())))
}
Declaration::TSImportEqualsDeclaration(decl) => {
if !check_binding || self.scope.has_reference(&decl.id.name) {
if !check_binding || self.scope.has_reference(decl.id.name) {
Some(Declaration::TSImportEqualsDeclaration(decl.clone_in(self.allocator())))
} else {
None
+2 -2
View File
@@ -650,7 +650,7 @@ impl<'a> IsolatedDeclarations<'a> {
emitted_function_overload_names.insert(name);
}
} else if (!is_internal || emitted_function_overload_names.contains(&name))
&& (include_unexported || self.scope.has_value_reference(&name))
&& (include_unexported || self.scope.has_value_reference(name))
{
can_expando_function_names.insert(name);
namespace_mergeable_function_names.insert(name);
@@ -663,7 +663,7 @@ impl<'a> IsolatedDeclarations<'a> {
if declarator.type_annotation.is_none()
&& declarator.init.as_ref().is_some_and(Expression::is_function)
&& let Some(name) = declarator.id.get_identifier_name()
&& (include_unexported || self.scope.has_value_reference(&name))
&& (include_unexported || self.scope.has_value_reference(name))
{
can_expando_function_names.insert(name);
}
@@ -1,7 +1,7 @@
use oxc_allocator::{ArenaBox, ArenaVec, CloneIn, GetAllocator, ReplaceWith};
use oxc_ast::ast::*;
use oxc_span::{GetSpan, SPAN};
use oxc_str::Str;
use oxc_str::{Ident, Str};
use crate::{IsolatedDeclarations, diagnostics::default_export_inferred};
@@ -43,13 +43,13 @@ impl<'a> IsolatedDeclarations<'a> {
}
pub(crate) fn create_unique_name(&self, name: &str) -> Str<'a> {
let mut binding = Str::from_str_in(name, self);
let mut binding = Ident::from_str_in(name, self);
let mut i = 1;
while self.scope.has_reference(&binding) {
binding = Str::from_str_in(format!("{name}_{i}").as_str(), self);
while self.scope.has_reference(binding) {
binding = Ident::from_str_in(format!("{name}_{i}").as_str(), self);
i += 1;
}
binding
binding.into()
}
pub(crate) fn transform_export_default_declaration(
@@ -157,13 +157,13 @@ impl<'a> IsolatedDeclarations<'a> {
specifiers.iter().for_each(|specifier| {
let is_referenced = match specifier {
ImportDeclarationSpecifier::ImportSpecifier(specifier) => {
self.scope.has_reference(&specifier.local.name)
self.scope.has_reference(specifier.local.name)
}
ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => {
self.scope.has_reference(&specifier.local.name)
self.scope.has_reference(specifier.local.name)
}
ImportDeclarationSpecifier::ImportNamespaceSpecifier(_) => {
self.scope.has_reference(&specifier.name())
self.scope.has_reference(specifier.name())
}
};
if is_referenced {
+24 -25
View File
@@ -1,11 +1,10 @@
use std::cell::Cell;
use bitflags::bitflags;
use rustc_hash::FxHashMap;
use oxc_ast::ast::*;
use oxc_ast_visit::{Visit, walk::*};
use oxc_str::Str;
use oxc_str::{Ident, IdentHashMap};
use oxc_syntax::scope::{ScopeFlags, ScopeId};
bitflags! {
@@ -20,14 +19,14 @@ bitflags! {
/// Declaration scope.
#[derive(Debug)]
struct Scope<'a> {
bindings: FxHashMap<Str<'a>, KindFlags>,
references: FxHashMap<Str<'a>, KindFlags>,
bindings: IdentHashMap<'a, KindFlags>,
references: IdentHashMap<'a, KindFlags>,
flags: ScopeFlags,
}
impl Scope<'_> {
fn new(flags: ScopeFlags) -> Self {
Self { bindings: FxHashMap::default(), references: FxHashMap::default(), flags }
Self { bindings: IdentHashMap::default(), references: IdentHashMap::default(), flags }
}
}
@@ -36,7 +35,7 @@ impl Scope<'_> {
pub struct ScopeTree<'a> {
levels: Vec<Scope<'a>>,
/// Pool of scopes whose maps have been emptied. Scopes are entered and left in a stack
/// pattern, so rather than dropping a left scope's two `FxHashMap`s (and allocating +
/// pattern, so rather than dropping a left scope's two maps (and allocating +
/// re-growing fresh ones on the next `enter_scope`), keep them here to reuse their heap
/// allocations and retained capacity.
free_scopes: Vec<Scope<'a>>,
@@ -53,23 +52,23 @@ impl<'a> ScopeTree<'a> {
scope.flags.contains(ScopeFlags::TsModuleBlock)
}
pub fn has_reference(&self, name: &str) -> bool {
pub fn has_reference(&self, name: Ident<'_>) -> bool {
let scope = self.levels.last().unwrap();
scope.references.contains_key(name)
scope.references.contains_key(&name)
}
/// Check if the current scope has a value reference for the given name.
pub fn has_value_reference(&self, name: &str) -> bool {
pub fn has_value_reference(&self, name: Ident<'_>) -> bool {
let scope = self.levels.last().unwrap();
scope.references.get(name).iter().any(|flags| flags.contains(KindFlags::Value))
scope.references.get(&name).iter().any(|flags| flags.contains(KindFlags::Value))
}
fn add_binding(&mut self, name: Str<'a>, flags: KindFlags) {
fn add_binding(&mut self, name: Ident<'a>, flags: KindFlags) {
let scope = self.levels.last_mut().unwrap();
scope.bindings.insert(name, flags);
}
fn add_reference(&mut self, name: Str<'a>, flags: KindFlags) {
fn add_reference(&mut self, name: Ident<'a>, flags: KindFlags) {
let scope = self.levels.last_mut().unwrap();
scope.references.entry(name).and_modify(|f| *f |= flags).or_insert(flags);
}
@@ -118,19 +117,19 @@ impl<'a> Visit<'a> for ScopeTree<'a> {
}
fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) {
self.add_reference(ident.name.into(), KindFlags::Value);
self.add_reference(ident.name, KindFlags::Value);
}
fn visit_binding_pattern(&mut self, pattern: &BindingPattern<'a>) {
if let BindingPattern::BindingIdentifier(ident) = pattern {
self.add_binding(ident.name.into(), KindFlags::Value);
self.add_binding(ident.name, KindFlags::Value);
}
walk_binding_pattern(self, pattern);
}
fn visit_ts_type_name(&mut self, name: &TSTypeName<'a>) {
if let TSTypeName::IdentifierReference(ident) = name {
self.add_reference(ident.name.into(), KindFlags::Type);
self.add_reference(ident.name, KindFlags::Type);
} else {
walk_ts_type_name(self, name);
}
@@ -140,7 +139,7 @@ impl<'a> Visit<'a> for ScopeTree<'a> {
fn visit_ts_type_query(&mut self, ty: &TSTypeQuery<'a>) {
if let Some(type_name) = ty.expr_name.as_ts_type_name() {
if let Some(ident) = TSTypeName::get_identifier_reference(type_name) {
self.add_reference(ident.name.into(), KindFlags::Value);
self.add_reference(ident.name, KindFlags::Value);
// `typeof Type<Parameters>`
// ^^^^^^^^^^^
if let Some(type_parameters) = &ty.type_arguments {
@@ -159,7 +158,7 @@ impl<'a> Visit<'a> for ScopeTree<'a> {
fn visit_export_named_declaration(&mut self, decl: &ExportNamedDeclaration<'a>) {
for specifier in &decl.specifiers {
if let Some(name) = specifier.local.identifier_name() {
self.add_reference(name.into(), KindFlags::All);
self.add_reference(name, KindFlags::All);
}
}
}
@@ -168,7 +167,7 @@ impl<'a> Visit<'a> for ScopeTree<'a> {
fn visit_export_default_declaration(&mut self, decl: &ExportDefaultDeclaration<'a>) {
if let ExportDefaultDeclarationKind::Identifier(ident) = &decl.declaration {
self.add_reference(ident.name.into(), KindFlags::All);
self.add_reference(ident.name, KindFlags::All);
} else {
walk_export_default_declaration(self, decl);
}
@@ -182,34 +181,34 @@ impl<'a> Visit<'a> for ScopeTree<'a> {
}
Declaration::FunctionDeclaration(decl) => {
if let Some(id) = decl.id.as_ref() {
self.add_binding(id.name.into(), KindFlags::Value);
self.add_binding(id.name, KindFlags::Value);
}
}
Declaration::ClassDeclaration(decl) => {
if let Some(id) = decl.id.as_ref() {
self.add_binding(id.name.into(), KindFlags::Value);
self.add_binding(id.name, KindFlags::Value);
}
}
Declaration::TSTypeAliasDeclaration(decl) => {
self.add_binding(decl.id.name.into(), KindFlags::Type);
self.add_binding(decl.id.name, KindFlags::Type);
}
Declaration::TSInterfaceDeclaration(decl) => {
self.add_binding(decl.id.name.into(), KindFlags::Type);
self.add_binding(decl.id.name, KindFlags::Type);
}
Declaration::TSEnumDeclaration(decl) => {
self.add_binding(decl.id.name.into(), KindFlags::All);
self.add_binding(decl.id.name, KindFlags::All);
}
Declaration::TSExternalModuleDeclaration(_) => {
// no binding
}
Declaration::TSNamespaceDeclaration(decl) => {
self.add_binding(decl.id.name.into(), KindFlags::All);
self.add_binding(decl.id.name, KindFlags::All);
}
Declaration::TSGlobalDeclaration(_) => {
// no binding
}
Declaration::TSImportEqualsDeclaration(decl) => {
self.add_binding(decl.id.name.into(), KindFlags::Value);
self.add_binding(decl.id.name, KindFlags::Value);
}
}
walk_declaration(self, declaration);