use std::{collections::hash_map::Entry, fmt, mem};
use rustc_hash::{FxHashMap, FxHashSet};
use self_cell::self_cell;
use oxc_allocator::{Allocator, CloneIn, Vec as ArenaVec};
use oxc_index::IndexVec;
use oxc_span::Span;
use oxc_str::{ArenaIdentHashMap, Ident};
use oxc_syntax::constant_value::ConstantValue;
use oxc_syntax::{
node::NodeId,
reference::{Reference, ReferenceId},
scope::{ScopeFlags, ScopeId},
symbol::{SymbolFlags, SymbolId},
};
use crate::multi_index_vec::multi_index_vec;
pub type Bindings<'a> = ArenaIdentHashMap<'a, SymbolId>;
pub type UnresolvedReferences<'a> = ArenaIdentHashMap<'a, ArenaVec<'a, ReferenceId>>;
#[derive(Clone, Debug)]
pub struct Redeclaration {
pub span: Span,
pub declaration: NodeId,
pub flags: SymbolFlags,
}
impl CloneIn<'_> for Redeclaration {
type Cloned = Self;
#[inline]
fn clone_in(&self, _allocator: &Allocator) -> Self::Cloned {
Self { span: self.span, declaration: NodeId::DUMMY, flags: self.flags }
}
#[inline]
fn clone_in_with_semantic_ids(&self, _allocator: &Allocator) -> Self::Cloned {
self.clone()
}
}
multi_index_vec! {
struct ScopeTable<ScopeId> {
parent_ids => parent_ids_mut: Option<ScopeId>,
node_ids => node_ids_mut: NodeId,
flags => flags_mut: ScopeFlags,
}
}
multi_index_vec! {
struct SymbolTable<SymbolId> {
symbol_spans => symbol_spans_mut: Span,
symbol_flags => symbol_flags_mut: SymbolFlags,
symbol_scope_ids => symbol_scope_ids_mut: ScopeId,
symbol_declarations => symbol_declarations_mut: NodeId,
}
}
use crate::ts_enum::EnumData;
pub struct Scoping {
symbol_table: SymbolTable,
pub(crate) references: IndexVec<ReferenceId, Reference>,
pub(crate) no_side_effects: FxHashSet<SymbolId>,
pub(crate) enum_data: EnumData,
scope_table: ScopeTable,
pub(crate) cell: ScopingCell,
}
impl fmt::Debug for Scoping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("Scoping").finish()
}
}
impl Default for Scoping {
fn default() -> Self {
Self {
symbol_table: SymbolTable::new(),
references: IndexVec::new(),
no_side_effects: FxHashSet::default(),
enum_data: EnumData::default(),
scope_table: ScopeTable::new(),
cell: ScopingCell::new(Allocator::default(), |allocator| ScopingInner {
symbol_names: ArenaVec::new_in(allocator),
resolved_references: ArenaVec::new_in(allocator),
symbol_redeclarations: FxHashMap::default(),
bindings: IndexVec::new(),
root_unresolved_references: UnresolvedReferences::new_in(allocator),
}),
}
}
}
mod scoping_cell {
use super::*;
self_cell!(
pub struct ScopingCellInner {
owner: Allocator,
#[covariant]
dependent: ScopingInner,
}
);
#[repr(transparent)]
pub struct ScopingCell(ScopingCellInner);
#[expect(clippy::inline_always)] impl ScopingCell {
#[inline(always)]
pub fn new(
allocator: Allocator,
dependent_builder: impl for<'_q> FnOnce(&'_q Allocator) -> ScopingInner<'_q>,
) -> Self {
Self(ScopingCellInner::new(allocator, dependent_builder))
}
#[inline(always)]
pub fn borrow_dependent(&self) -> &ScopingInner<'_> {
self.0.borrow_dependent()
}
#[inline(always)]
pub fn with_dependent_mut<'outer_fn, Ret>(
&'outer_fn mut self,
func: impl for<'_q> FnOnce(&'_q Allocator, &'outer_fn mut ScopingInner<'_q>) -> Ret,
) -> Ret {
self.0.with_dependent_mut(func)
}
#[expect(clippy::unnecessary_safety_comment)]
#[inline(always)]
pub fn allocator_used_bytes(&self) -> usize {
self.0.borrow_owner().used_bytes()
}
#[expect(dead_code)]
#[inline(always)]
pub fn into_owner(self) -> Allocator {
self.0.into_owner()
}
}
unsafe impl Send for ScopingCell {}
unsafe impl<'cell> Sync for ScopingCell where ScopingInner<'cell>: Sync {}
}
use scoping_cell::ScopingCell;
pub struct ScopingInner<'cell> {
symbol_names: ArenaVec<'cell, Ident<'cell>>,
resolved_references: ArenaVec<'cell, ArenaVec<'cell, ReferenceId>>,
symbol_redeclarations: FxHashMap<SymbolId, ArenaVec<'cell, Redeclaration>>,
pub(crate) bindings: IndexVec<ScopeId, Bindings<'cell>>,
pub(crate) root_unresolved_references: UnresolvedReferences<'cell>,
}
impl Scoping {
#[inline]
pub fn symbols_len(&self) -> usize {
self.symbol_table.len()
}
#[inline]
pub fn symbols_is_empty(&self) -> bool {
self.symbol_table.is_empty()
}
pub fn symbol_names(&self) -> impl Iterator<Item = &str> + '_ {
self.cell.borrow_dependent().symbol_names.iter().map(Ident::as_str)
}
pub fn resolved_references(&self) -> impl Iterator<Item = &ArenaVec<'_, ReferenceId>> + '_ {
self.cell.borrow_dependent().resolved_references.iter()
}
pub fn symbol_ids(&self) -> impl Iterator<Item = SymbolId> + '_ {
self.symbol_table.iter_ids()
}
#[inline]
pub fn symbol_span(&self, symbol_id: SymbolId) -> Span {
*self.symbol_table.symbol_spans(symbol_id)
}
#[inline]
pub fn symbol_name(&self, symbol_id: SymbolId) -> &str {
self.cell.borrow_dependent().symbol_names[symbol_id.index()].as_str()
}
#[inline]
pub fn symbol_ident(&self, symbol_id: SymbolId) -> Ident<'_> {
self.cell.borrow_dependent().symbol_names[symbol_id.index()]
}
#[inline]
pub fn set_symbol_name(&mut self, symbol_id: SymbolId, name: Ident<'_>) {
self.cell.with_dependent_mut(|allocator, cell| {
cell.symbol_names[symbol_id.index()] = name.clone_in(allocator);
});
}
#[inline]
pub fn symbol_flags(&self, symbol_id: SymbolId) -> SymbolFlags {
*self.symbol_table.symbol_flags(symbol_id)
}
#[inline]
pub fn symbol_flags_mut(&mut self, symbol_id: SymbolId) -> &mut SymbolFlags {
self.symbol_table.symbol_flags_mut(symbol_id)
}
#[inline]
pub fn symbol_redeclarations(&self, symbol_id: SymbolId) -> &[Redeclaration] {
self.cell.borrow_dependent().symbol_redeclarations.get(&symbol_id).map_or_else(
|| {
static EMPTY: &[Redeclaration] = &[];
EMPTY
},
|v| v.as_slice(),
)
}
#[inline]
pub fn union_symbol_flag(&mut self, symbol_id: SymbolId, includes: SymbolFlags) {
*self.symbol_table.symbol_flags_mut(symbol_id) |= includes;
}
#[inline]
pub fn set_symbol_scope_id(&mut self, symbol_id: SymbolId, scope_id: ScopeId) {
*self.symbol_table.symbol_scope_ids_mut(symbol_id) = scope_id;
}
#[inline]
pub fn symbol_scope_id(&self, symbol_id: SymbolId) -> ScopeId {
*self.symbol_table.symbol_scope_ids(symbol_id)
}
#[inline]
pub fn symbol_declaration(&self, symbol_id: SymbolId) -> NodeId {
*self.symbol_table.symbol_declarations(symbol_id)
}
#[inline]
pub fn symbol_declarations(&self, symbol_id: SymbolId) -> impl Iterator<Item = NodeId> + '_ {
let redeclarations = self.symbol_redeclarations(symbol_id);
std::iter::once(self.symbol_declaration(symbol_id))
.filter(move |_| redeclarations.is_empty())
.chain(redeclarations.iter().map(|redeclaration| redeclaration.declaration))
}
pub fn create_symbol(
&mut self,
span: Span,
name: Ident<'_>,
flags: SymbolFlags,
scope_id: ScopeId,
node_id: NodeId,
) -> SymbolId {
self.cell.with_dependent_mut(|allocator, cell| {
cell.symbol_names.push(name.clone_in(allocator));
cell.resolved_references.push(ArenaVec::new_in(allocator));
});
self.symbol_table.push(span, flags, scope_id, node_id)
}
pub fn add_symbol_redeclaration(
&mut self,
symbol_id: SymbolId,
flags: SymbolFlags,
declaration: NodeId,
span: Span,
) {
let is_first_redeclared =
!self.cell.borrow_dependent().symbol_redeclarations.contains_key(&symbol_id);
let first_declaration = is_first_redeclared.then(|| Redeclaration {
span: self.symbol_span(symbol_id),
declaration: self.symbol_declaration(symbol_id),
flags: self.symbol_flags(symbol_id),
});
self.cell.with_dependent_mut(|allocator, cell| {
let redeclaration = Redeclaration { span, declaration, flags };
match cell.symbol_redeclarations.entry(symbol_id) {
Entry::Occupied(occupied) => {
occupied.into_mut().push(redeclaration);
}
Entry::Vacant(vacant) => {
let first_declaration = first_declaration.unwrap_or_else(|| {
unreachable!(
"The above step has already been checked, and it was first declared."
)
});
let v = ArenaVec::from_array_in([first_declaration, redeclaration], allocator);
vacant.insert(v);
}
}
});
}
#[inline]
pub fn create_reference(&mut self, reference: Reference) -> ReferenceId {
self.references.push(reference)
}
#[inline]
pub fn get_reference(&self, reference_id: ReferenceId) -> &Reference {
&self.references[reference_id]
}
#[inline]
pub fn get_reference_mut(&mut self, reference_id: ReferenceId) -> &mut Reference {
&mut self.references[reference_id]
}
#[inline]
pub fn get_reference_name(&self, reference_id: ReferenceId) -> Option<&str> {
self.symbol_name(self.references[reference_id].symbol_id()?).into()
}
#[inline]
pub fn has_binding(&self, reference_id: ReferenceId) -> bool {
self.references[reference_id].symbol_id().is_some()
}
#[inline]
pub fn get_resolved_reference_ids(&self, symbol_id: SymbolId) -> &[ReferenceId] {
&self.cell.borrow_dependent().resolved_references[symbol_id.index()]
}
pub fn get_resolved_references(
&self,
symbol_id: SymbolId,
) -> impl DoubleEndedIterator<Item = &Reference> + '_ {
self.get_resolved_reference_ids(symbol_id)
.iter()
.map(|&reference_id| &self.references[reference_id])
}
pub fn symbol_is_mutated(&self, symbol_id: SymbolId) -> bool {
if self.symbol_table.symbol_flags(symbol_id).contains(SymbolFlags::ConstVariable) {
false
} else {
self.get_resolved_references(symbol_id).any(Reference::is_write)
}
}
pub fn symbol_is_unused(&self, symbol_id: SymbolId) -> bool {
self.get_resolved_reference_ids(symbol_id).is_empty()
}
pub fn add_resolved_reference(&mut self, symbol_id: SymbolId, reference_id: ReferenceId) {
self.cell.with_dependent_mut(|_allocator, cell| {
cell.resolved_references[symbol_id.index()].push(reference_id);
});
}
pub fn delete_reference(&mut self, reference_id: ReferenceId) {
let Some(symbol_id) = self.get_reference(reference_id).symbol_id() else { return };
self.delete_resolved_reference(symbol_id, reference_id);
}
pub fn delete_resolved_reference(&mut self, symbol_id: SymbolId, reference_id: ReferenceId) {
self.cell.with_dependent_mut(|_allocator, cell| {
let reference_ids = &mut cell.resolved_references[symbol_id.index()];
let index = reference_ids.iter().position(|&id| id == reference_id).unwrap();
reference_ids.swap_remove(index);
});
}
pub fn retain_resolved_references(&mut self, live_references: &FxHashSet<ReferenceId>) {
self.cell.with_dependent_mut(|_allocator, cell| {
for reference_ids in &mut cell.resolved_references {
reference_ids.retain(|id| live_references.contains(id));
}
});
}
pub fn reserve(
&mut self,
additional_symbols: usize,
additional_references: usize,
additional_scopes: usize,
) {
self.symbol_table.reserve(additional_symbols);
self.cell.with_dependent_mut(|_allocator, cell| {
cell.symbol_names.reserve_exact(additional_symbols);
cell.resolved_references.reserve_exact(additional_symbols);
});
self.references.reserve(additional_references);
self.scope_table.reserve(additional_scopes);
self.cell.with_dependent_mut(|_allocator, cell| {
cell.bindings.reserve(additional_scopes);
});
}
pub fn no_side_effects(&self) -> &FxHashSet<SymbolId> {
&self.no_side_effects
}
pub fn get_enum_member_value(&self, symbol_id: SymbolId) -> Option<&ConstantValue> {
self.enum_data.get_member_value(symbol_id)
}
pub(crate) fn set_enum_member_value(&mut self, symbol_id: SymbolId, value: ConstantValue) {
self.enum_data.set_member_value(symbol_id, value);
}
pub fn get_enum_body_scopes(&self, symbol_id: SymbolId) -> Option<&[ScopeId]> {
self.enum_data.get_body_scopes(symbol_id)
}
pub(crate) fn add_enum_body_scope(&mut self, symbol_id: SymbolId, scope_id: ScopeId) {
self.enum_data.add_body_scope(symbol_id, scope_id);
}
}
impl Scoping {
const ROOT_SCOPE_ID: ScopeId = ScopeId::new(0);
#[inline]
pub fn scopes_len(&self) -> usize {
self.scope_table.len()
}
#[inline]
pub fn scopes_is_empty(&self) -> bool {
self.scope_table.is_empty()
}
pub fn scope_ancestors(&self, scope_id: ScopeId) -> impl Iterator<Item = ScopeId> + '_ {
std::iter::successors(Some(scope_id), |&scope_id| *self.scope_table.parent_ids(scope_id))
}
pub fn scope_descendants_from_root(&self) -> impl Iterator<Item = ScopeId> + '_ {
self.scope_table.iter_ids()
}
#[expect(clippy::unused_self)]
#[inline]
pub const fn root_scope_id(&self) -> ScopeId {
Self::ROOT_SCOPE_ID
}
#[inline]
pub fn root_scope_flags(&self) -> ScopeFlags {
*self.scope_table.flags(self.root_scope_id())
}
#[inline]
pub fn root_unresolved_references(&self) -> &UnresolvedReferences<'_> {
&self.cell.borrow_dependent().root_unresolved_references
}
pub fn root_unresolved_references_ids(
&self,
) -> impl Iterator<Item = impl Iterator<Item = ReferenceId> + '_> + '_ {
self.cell.borrow_dependent().root_unresolved_references.values().map(|v| v.iter().copied())
}
#[inline]
pub fn delete_root_unresolved_reference(&mut self, name: Ident<'_>, reference_id: ReferenceId) {
self.cell.with_dependent_mut(|_allocator, cell| {
let reference_ids = cell.root_unresolved_references.get_mut(name.as_str()).unwrap();
if reference_ids.len() == 1 {
assert_eq!(reference_ids[0], reference_id);
cell.root_unresolved_references.remove(name.as_str());
} else {
let index = reference_ids.iter().position(|&id| id == reference_id).unwrap();
reference_ids.swap_remove(index);
}
});
}
#[inline]
pub fn scope_flags(&self, scope_id: ScopeId) -> ScopeFlags {
*self.scope_table.flags(scope_id)
}
#[inline]
pub fn scope_flags_mut(&mut self, scope_id: ScopeId) -> &mut ScopeFlags {
self.scope_table.flags_mut(scope_id)
}
pub fn get_new_scope_flags(&self, flags: ScopeFlags, parent_scope_id: ScopeId) -> ScopeFlags {
flags | self.scope_flags(parent_scope_id) & ScopeFlags::StrictMode
}
#[inline]
pub fn scope_parent_id(&self, scope_id: ScopeId) -> Option<ScopeId> {
*self.scope_table.parent_ids(scope_id)
}
pub fn set_scope_parent_id(&mut self, scope_id: ScopeId, parent_id: Option<ScopeId>) {
*self.scope_table.parent_ids_mut(scope_id) = parent_id;
}
pub fn change_scope_parent_id(&mut self, scope_id: ScopeId, new_parent_id: Option<ScopeId>) {
*self.scope_table.parent_ids_mut(scope_id) = new_parent_id;
}
#[inline]
pub fn get_root_binding(&self, name: Ident<'_>) -> Option<SymbolId> {
self.get_binding(self.root_scope_id(), name)
}
pub fn add_root_unresolved_reference(&mut self, name: Ident<'_>, reference_id: ReferenceId) {
self.cell.with_dependent_mut(|allocator, cell| {
let name = name.clone_in(allocator);
cell.root_unresolved_references
.entry(name)
.or_insert_with(|| ArenaVec::new_in(allocator))
.push(reference_id);
});
}
pub fn scope_has_binding(&self, scope_id: ScopeId, name: Ident<'_>) -> bool {
self.cell.borrow_dependent().bindings[scope_id].contains_key(&name)
}
pub fn get_binding(&self, scope_id: ScopeId, name: Ident<'_>) -> Option<SymbolId> {
self.cell.borrow_dependent().bindings[scope_id].get(&name).copied()
}
pub fn find_binding(&self, mut scope_id: ScopeId, name: Ident<'_>) -> Option<SymbolId> {
let cell = self.cell.borrow_dependent();
loop {
if let Some(symbol_id) = cell.bindings[scope_id].get(&name) {
return Some(*symbol_id);
}
let parent_scope_id = (*self.scope_table.parent_ids(scope_id))?;
scope_id = parent_scope_id;
}
}
#[inline]
pub fn get_bindings(&self, scope_id: ScopeId) -> &Bindings<'_> {
&self.cell.borrow_dependent().bindings[scope_id]
}
#[inline]
pub fn get_node_id(&self, scope_id: ScopeId) -> NodeId {
*self.scope_table.node_ids(scope_id)
}
pub fn iter_bindings(&self) -> impl Iterator<Item = (ScopeId, &Bindings<'_>)> + '_ {
self.cell.borrow_dependent().bindings.iter_enumerated()
}
#[inline]
pub fn iter_bindings_in(&self, scope_id: ScopeId) -> impl Iterator<Item = SymbolId> + '_ {
self.cell.borrow_dependent().bindings[scope_id].values().copied()
}
#[inline]
pub(crate) fn insert_binding(
&mut self,
scope_id: ScopeId,
name: Ident<'_>,
symbol_id: SymbolId,
) {
self.cell.with_dependent_mut(|allocator, cell| {
let name = name.clone_in(allocator);
cell.bindings[scope_id].insert(name, symbol_id);
});
}
#[inline]
pub fn add_scope(
&mut self,
parent_id: Option<ScopeId>,
node_id: NodeId,
flags: ScopeFlags,
) -> ScopeId {
let scope_id = self.scope_table.push(parent_id, node_id, flags);
self.cell.with_dependent_mut(|allocator, cell| {
cell.bindings.push(Bindings::new_in(allocator));
});
scope_id
}
pub fn add_binding(&mut self, scope_id: ScopeId, name: Ident<'_>, symbol_id: SymbolId) {
self.cell.with_dependent_mut(|allocator, cell| {
let name = name.clone_in(allocator);
cell.bindings[scope_id].insert(name, symbol_id);
});
}
pub fn remove_binding(&mut self, scope_id: ScopeId, name: Ident<'_>) {
self.cell.with_dependent_mut(|_allocator, cell| {
cell.bindings[scope_id].remove(name.as_str());
});
}
pub fn move_binding(&mut self, from: ScopeId, to: ScopeId, name: Ident<'_>) {
self.cell.with_dependent_mut(|_allocator, cell| {
let from_map = &mut cell.bindings[from];
if let Some((name, symbol_id)) = from_map.remove_entry(name.as_str()) {
cell.bindings[to].insert(name, symbol_id);
}
});
}
pub fn rename_binding(
&mut self,
scope_id: ScopeId,
symbol_id: SymbolId,
old_name: Ident<'_>,
new_name: Ident<'_>,
) {
self.cell.with_dependent_mut(|allocator, cell| {
let bindings = &mut cell.bindings[scope_id];
let old_symbol_id = bindings.remove(old_name.as_str());
debug_assert_eq!(old_symbol_id, Some(symbol_id));
let new_name = new_name.clone_in(allocator);
let existing_symbol_id = bindings.insert(new_name, symbol_id);
debug_assert!(existing_symbol_id.is_none());
});
}
pub fn rename_symbol(&mut self, symbol_id: SymbolId, scope_id: ScopeId, new_name: Ident<'_>) {
self.cell.with_dependent_mut(|allocator, cell| {
let new_name = new_name.clone_in(allocator);
let old_name = mem::replace(&mut cell.symbol_names[symbol_id.index()], new_name);
let bindings = &mut cell.bindings[scope_id];
let old_symbol_id = bindings.remove(old_name.as_str());
debug_assert_eq!(old_symbol_id, Some(symbol_id));
let existing_symbol_id = bindings.insert(new_name, symbol_id);
debug_assert!(existing_symbol_id.is_none());
});
}
pub fn delete_typescript_bindings(&mut self) {
self.cell.with_dependent_mut(|_allocator, cell| {
for bindings in &mut cell.bindings {
bindings.retain(|_name, symbol_id| {
let flags = *self.symbol_table.symbol_flags(*symbol_id);
!flags.intersects(
SymbolFlags::TypeAlias
| SymbolFlags::Interface
| SymbolFlags::TypeParameter,
)
});
}
});
}
}
impl Scoping {
#[must_use]
pub fn clone_in_with_semantic_ids_with_another_arena(&self) -> Self {
let used_bytes = self.cell.allocator_used_bytes();
let cell = self.cell.borrow_dependent();
Self {
symbol_table: self.symbol_table.clone(),
references: self.references.clone(),
no_side_effects: self.no_side_effects.clone(),
enum_data: self.enum_data.clone(),
scope_table: self.scope_table.clone(),
cell: {
let allocator = Allocator::with_capacity(used_bytes);
ScopingCell::new(allocator, |allocator| ScopingInner {
symbol_names: cell.symbol_names.clone_in_with_semantic_ids(allocator),
resolved_references: cell
.resolved_references
.clone_in_with_semantic_ids(allocator),
symbol_redeclarations: cell
.symbol_redeclarations
.iter()
.map(|(k, v)| (*k, v.clone_in_with_semantic_ids(allocator)))
.collect(),
bindings: cell
.bindings
.iter()
.map(|map| map.clone_in_with_semantic_ids(allocator))
.collect(),
root_unresolved_references: cell
.root_unresolved_references
.clone_in_with_semantic_ids(allocator),
})
},
}
}
}