oxc_semantic 0.125.0

A collection of JavaScript tools written in Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
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! {
    /// Scope tree stored as struct-of-arrays in a single allocation.
    ///
    /// Contains parent IDs, node IDs, and flags for all scopes. Using a single
    /// allocation with one `len`/`cap` instead of 3 separate `IndexVec`s saves
    /// memory (no redundant len/cap) and CPU (one bounds check, one capacity
    /// check on push).
    struct ScopeTable<ScopeId> {
        parent_ids => parent_ids_mut: Option<ScopeId>,
        node_ids => node_ids_mut: NodeId,
        flags => flags_mut: ScopeFlags,
    }
}

multi_index_vec! {
    /// Symbol table stored as struct-of-arrays in a single allocation.
    ///
    /// Contains spans, flags, scope IDs, and declaration node IDs for all symbols.
    /// Using a single allocation with one `len`/`cap` instead of 4 separate `IndexVec`s
    /// saves memory and CPU.
    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,
    }
}

/// # Symbol Table and Scope Tree
///
/// ## Symbol Table
///
/// `SoA` (Struct of Arrays) for memory efficiency.
///
/// Most symbols won't have redeclarations, so instead of storing `Vec<Span>` directly in
/// `redeclare_variables` (32 bytes per symbol), store `Option<RedeclarationId>` (4 bytes).
/// That ID indexes into `redeclarations` where the actual `Vec<Span>` is stored.
///
/// ## Scope Tree
///
use crate::ts_enum::EnumData;

/// The scope tree stores lexical scopes created by a program, and all the
/// variable bindings each scope creates.
///
/// - All scopes have a parent scope, except the root scope.
/// - Scopes can have 0 or more child scopes.
/// - Nodes that create a scope store the [`ScopeId`] of the scope they create.
pub struct Scoping {
    /* Symbol Table - single allocation for all symbol-indexed flat fields */
    symbol_table: SymbolTable,

    pub(crate) references: IndexVec<ReferenceId, Reference>,

    /// Function or Variable Symbol IDs that are marked with `@__NO_SIDE_EFFECTS__`.
    pub(crate) no_side_effects: FxHashSet<SymbolId>,

    /// Pre-computed enum member values and scope mappings.
    pub(crate) enum_data: EnumData,

    /* Scope Tree - single allocation for all scope-indexed flat fields */
    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),
            }),
        }
    }
}

/// [`ScopingCell`] contains parts of [`Scoping`] which are 2-dimensional structures
/// e.g. `Vec<Vec<T>>`, `HashMap<Vec<T>>`, `Vec<HashMap<T>>`.
///
/// These structures are very expensive to construct and drop, due to the large number of allocations /
/// deallocations involved. Therefore, we store them in an arena allocator to:
/// 1. Avoid costly heap allocations.
/// 2. Be able to drop all the data cheaply in one go.
///
/// We use [`self_cell!`] to be able to store the `Allocator` alongside `Vec`s and `HashMap`s which store
/// their data in that `Allocator` (self-referential struct).
///
/// Conceptually, these structures own their data, and so `ScopingCell` (and therefore also `Scoping`)
/// should be `Send` and `Sync`, exactly as it would be if `ScopingCell` contained standard heap-allocated
/// `Vec`s and `HashMap`s.
///
/// However, the use of an arena allocator complicates matters, because `Allocator` is not `Sync`,
/// and `oxc_allocator::Vec` is not `Send`.
///
/// ### `Sync`
///
/// For it to be safe for `&ScopingCell` to be sent across threads, we must make it impossible to obtain
/// multiple `&Allocator` references from them on different threads, because those references could be
/// used to allocate into the same arena simultaneously. `Allocator` is not thread-safe, and this would
/// likely be undefined behavior.
///
/// We prevent this by wrapping the struct created by `self_cell!` in a further wrapper.
/// That outer wrapper prevents access to `with_dependent` and `borrow_owner` methods of `ScopingCellInner`,
/// which allow obtaining `&Allocator` from a `&ScopingCell`.
///
/// The only method which *does* allow access to `&Allocator` is `with_dependent_mut`.
/// It takes `&mut self`, which guarantees exclusive access to `ScopingCell`. Therefore, no other code
/// (on any thread) can simultaneously have access to the `Allocator` during a call to `with_dependent_mut`.
///
/// `allocator_used_bytes` obtains an `&Allocator` reference internally, without taking `&mut self`.
/// But it doesn't mutate the `Allocator` in any way, and it doesn't expose the `&Allocator` to user.
/// By taking `&self`, it guarantees that `with_dependent_mut` cannot be called at the same time.
///
/// ### `Send`
///
/// `Allocator` is `Send`. `oxc_allocator::Vec` is not, but that restriction is purely to prevent a `Vec`
/// being moved to different thread from the `Allocator`, which would allow multiple threads making
/// allocations in that arena simultaneously.
///
/// Here, the `Allocator` and the `Vec`s are contained in the same struct, and moving them to another
/// thread *together* does not cause a problem.
///
/// This is all enclosed in a module, to prevent access to `ScopingCellInner` directly.
mod scoping_cell {
    use super::*;

    // Inner self-referential struct containing `Allocator` and `ScopingInner`,
    // where `ScopingInner` contains `Vec`s and `HashMap`s which store their data in the `Allocator`.
    self_cell!(
        pub struct ScopingCellInner {
            owner: Allocator,
            #[covariant]
            dependent: ScopingInner,
        }
    );

    /// Wrapper around [`ScopingCellInner`], which only provides methods that give access to an
    /// `&Allocator` reference if provided with `&mut ScopingCell`. See comments above.
    #[repr(transparent)]
    pub struct ScopingCell(ScopingCellInner);

    #[expect(clippy::inline_always)] // All methods just delegate
    impl ScopingCell {
        /// Construct a new [`ScopingCell`] with an [`Allocator`] and `dependent_builder` function.
        #[inline(always)]
        pub fn new(
            allocator: Allocator,
            dependent_builder: impl for<'_q> FnOnce(&'_q Allocator) -> ScopingInner<'_q>,
        ) -> Self {
            Self(ScopingCellInner::new(allocator, dependent_builder))
        }

        /// Borrow [`ScopingInner`].
        #[inline(always)]
        pub fn borrow_dependent(&self) -> &ScopingInner<'_> {
            self.0.borrow_dependent()
        }

        /// Call given closure `func` with an unique reference to [`ScopingInner`].
        #[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)
        }

        /// Calculate the total size of data used in the [`Allocator`], in bytes.
        ///
        /// See [`Allocator::used_bytes`] for more info.
        #[expect(clippy::unnecessary_safety_comment)]
        #[inline(always)]
        pub fn allocator_used_bytes(&self) -> usize {
            // SAFETY:
            // `with_dependent_mut` is the only method which gives access to the `Allocator`, and it
            // takes `&mut self`. This method takes `&self`, which means it can't be called at the same
            // time as `with_dependent_mut` (or within `with_dependent_mut`'s callback closure).
            //
            // Therefore, the only other references to `&Allocator` which can be held at this point
            // are in other calls to this method on other threads.
            // `used_bytes` does not perform allocations, or mutate the `Allocator` in any way.
            // So it's fine if 2 threads are calling this method simultaneously, because they're
            // both performing read-only actions.
            //
            // Another thread could simultaneously hold a reference to `&ScopingInner` via `borrow_dependent`,
            // but the `Vec`s and `HashMap`s in `ScopingInner` don't allow making allocations in the arena
            // without a `&mut` reference (e.g. `Vec::push` takes `&mut self`). Such mutable references
            // cannot be obtained from an immutable `&ScopingInner` reference.
            // So there's no way for simultaneous usage of `borrow_dependent` on another thread to break
            // the guarantee that no mutation of the `Allocator` can occur during this method.
            self.0.borrow_owner().used_bytes()
        }

        /// Consume [`ScopingCell`] and return the [`Allocator`] it contains.
        #[expect(dead_code)]
        #[inline(always)]
        pub fn into_owner(self) -> Allocator {
            self.0.into_owner()
        }
    }

    /// SAFETY: `ScopingCell` can be `Send` because both the `Allocator` and `Vec`s / `HashMap`s
    /// storing their data in that `Allocator` are moved to another thread together.
    unsafe impl Send for ScopingCell {}

    /// SAFETY: `ScopingCell` can be `Sync` if `ScopingInner` is `Sync`, because `ScopingCell` provides
    /// no methods which give access to an `&Allocator` reference, except when taking `&mut self`,
    /// which guarantees exclusive access. See further explanation above.
    unsafe impl<'cell> Sync for ScopingCell where ScopingInner<'cell>: Sync {}
}
use scoping_cell::ScopingCell;

pub struct ScopingInner<'cell> {
    /* Symbol Table Fields */
    symbol_names: ArenaVec<'cell, Ident<'cell>>,
    resolved_references: ArenaVec<'cell, ArenaVec<'cell, ReferenceId>>,
    /// Redeclarations of a symbol.
    ///
    /// NOTE:
    /// Once a symbol is redeclared, there are at least two entries here. The first
    /// entry is the original symbol information, and the rest are redeclarations.
    /// i.e. `symbol_redeclarations[symbol_id].len() >= 2` always.
    symbol_redeclarations: FxHashMap<SymbolId, ArenaVec<'cell, Redeclaration>>,
    /* Scope Tree Fields */
    /// Symbol bindings in a scope.
    ///
    /// A binding is a mapping from an identifier name to its [`SymbolId`]
    pub(crate) bindings: IndexVec<ScopeId, Bindings<'cell>>,

    pub(crate) root_unresolved_references: UnresolvedReferences<'cell>,
}

// Symbol Table Methods
impl Scoping {
    /// Returns the number of symbols in this table.
    #[inline]
    pub fn symbols_len(&self) -> usize {
        self.symbol_table.len()
    }

    /// Returns `true` if this table contains no symbols.
    #[inline]
    pub fn symbols_is_empty(&self) -> bool {
        self.symbol_table.is_empty()
    }

    /// Iterate all symbol names in insertion order.
    pub fn symbol_names(&self) -> impl Iterator<Item = &str> + '_ {
        self.cell.borrow_dependent().symbol_names.iter().map(Ident::as_str)
    }

    /// Iterate resolved reference ID lists for each symbol.
    pub fn resolved_references(&self) -> impl Iterator<Item = &ArenaVec<'_, ReferenceId>> + '_ {
        self.cell.borrow_dependent().resolved_references.iter()
    }

    /// Iterate over all symbol IDs in this table.
    ///
    /// Use [`ScopeTree::iter_bindings_in`] to only iterate over symbols declared in a specific
    /// scope.
    ///
    /// [`ScopeTree::iter_bindings_in`]: crate::scoping::Scoping::iter_bindings_in
    pub fn symbol_ids(&self) -> impl Iterator<Item = SymbolId> + '_ {
        self.symbol_table.iter_ids()
    }

    /// Get the [`Span`] of the [`AstNode`] declaring a symbol.
    ///
    /// [`AstNode`]: crate::node::AstNode
    #[inline]
    pub fn symbol_span(&self, symbol_id: SymbolId) -> Span {
        *self.symbol_table.symbol_spans(symbol_id)
    }

    /// Get the identifier name a symbol is bound to.
    #[inline]
    pub fn symbol_name(&self, symbol_id: SymbolId) -> &str {
        self.cell.borrow_dependent().symbol_names[symbol_id.index()].as_str()
    }

    /// Get the [`Ident`] for the identifier name a symbol is bound to.
    #[inline]
    pub fn symbol_ident(&self, symbol_id: SymbolId) -> Ident<'_> {
        self.cell.borrow_dependent().symbol_names[symbol_id.index()]
    }

    /// Rename a symbol.
    ///
    /// Returns the old name.
    #[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);
        });
    }

    /// Get the [`SymbolFlags`] for a symbol, which describe how the symbol is declared.
    ///
    /// To find how a symbol is used, use [`Scoping::get_resolved_references`].
    #[inline]
    pub fn symbol_flags(&self, symbol_id: SymbolId) -> SymbolFlags {
        *self.symbol_table.symbol_flags(symbol_id)
    }

    /// Get a mutable reference to a symbol's [flags](SymbolFlags).
    #[inline]
    pub fn symbol_flags_mut(&mut self, symbol_id: SymbolId) -> &mut SymbolFlags {
        self.symbol_table.symbol_flags_mut(symbol_id)
    }

    #[inline]
    /// Get all redeclarations associated with a symbol.
    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]
    /// Union additional flags into an existing symbol.
    pub fn union_symbol_flag(&mut self, symbol_id: SymbolId, includes: SymbolFlags) {
        *self.symbol_table.symbol_flags_mut(symbol_id) |= includes;
    }

    #[inline]
    /// Set the scope that owns `symbol_id`.
    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]
    /// Get the scope that owns `symbol_id`.
    pub fn symbol_scope_id(&self, symbol_id: SymbolId) -> ScopeId {
        *self.symbol_table.symbol_scope_ids(symbol_id)
    }

    /// Get the ID of the AST node declaring a symbol.
    ///
    /// This node will be a [`VariableDeclaration`], [`Function`], or some other AST node
    /// that _has_ a [`BindingIdentifier`] or a [`BindingPattern`]. It will not point to the
    /// binding pattern or identifier node itself.
    ///
    /// [`VariableDeclaration`]: oxc_ast::ast::VariableDeclaration
    /// [`Function`]: oxc_ast::ast::Function
    /// [`BindingIdentifier`]: oxc_ast::ast::BindingIdentifier
    /// [`BindingPattern`]: oxc_ast::ast::BindingPattern
    #[inline]
    pub fn symbol_declaration(&self, symbol_id: SymbolId) -> NodeId {
        *self.symbol_table.symbol_declarations(symbol_id)
    }

    /// Get all declaration node IDs for a symbol.
    ///
    /// Most symbols have a single declaration, but TypeScript allows merged type/value
    /// declarations (e.g. `interface Foo {}` + `const Foo = ...`).
    /// This iterator yields all declaration nodes in those cases.
    #[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))
    }

    /// Create a new symbol and append symbol metadata to the symbol table.
    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)
    }

    /// Record a redeclaration for an existing symbol.
    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);
        // Borrow checker doesn't allow us to call `self.symbol_span` in `with_dependent_mut`,
        // so we need construct `Redeclaration` here.
        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]
    /// Create and store a reference entry.
    pub fn create_reference(&mut self, reference: Reference) -> ReferenceId {
        self.references.push(reference)
    }

    /// Get a resolved or unresolved reference.
    ///
    /// [`ReferenceId`]s can be found in [oxc_ast::ast::IdentifierReference] and similar nodes.
    #[inline]
    pub fn get_reference(&self, reference_id: ReferenceId) -> &Reference {
        &self.references[reference_id]
    }

    #[inline]
    /// Get mutable access to a reference.
    pub fn get_reference_mut(&mut self, reference_id: ReferenceId) -> &mut Reference {
        &mut self.references[reference_id]
    }

    /// Get the name of the symbol a reference is resolved to. Returns `None` if the reference is
    /// not resolved.
    #[inline]
    pub fn get_reference_name(&self, reference_id: ReferenceId) -> Option<&str> {
        self.symbol_name(self.references[reference_id].symbol_id()?).into()
    }

    /// Returns `true` if the corresponding [`Reference`] is resolved to a symbol.
    ///
    /// When `false`, this could either be a reference to a global value or an identifier that does
    /// not exist.
    #[inline]
    pub fn has_binding(&self, reference_id: ReferenceId) -> bool {
        self.references[reference_id].symbol_id().is_some()
    }

    /// Find [`Reference`] ids resolved to a symbol.
    ///
    /// If you want direct access to a symbol's [`Reference`]s, use [`Scoping::get_resolved_references`].
    #[inline]
    pub fn get_resolved_reference_ids(&self, symbol_id: SymbolId) -> &[ReferenceId] {
        &self.cell.borrow_dependent().resolved_references[symbol_id.index()]
    }

    /// Find [`Reference`]s resolved to a symbol.
    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])
    }

    /// Get whether a symbol is mutated (i.e. assigned to).
    ///
    /// If symbol is `const`, always returns `false`.
    /// Otherwise, returns `true` if the symbol is assigned to somewhere in AST.
    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)
        }
    }

    /// Get whether a symbol is unused (i.e. not read or written after declaration).
    pub fn symbol_is_unused(&self, symbol_id: SymbolId) -> bool {
        self.get_resolved_reference_ids(symbol_id).is_empty()
    }

    /// Add a reference to a symbol.
    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);
        });
    }

    /// Delete a reference.
    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);
    }

    /// Delete a reference to a symbol.
    ///
    /// # Panics
    /// Panics if provided `reference_id` is not a resolved reference for `symbol_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);
        });
    }

    /// Retain only resolved references that are in the given set.
    ///
    /// This is an O(n) batch operation across all symbols, much more efficient than
    /// calling `delete_resolved_reference` repeatedly when many references from the
    /// same symbol need to be removed (which would be O(n²) due to the linear scan
    /// in each deletion).
    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));
            }
        });
    }

    /// Reserve additional capacity for symbols, references, and scopes.
    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);
        });
    }

    /// Symbols marked with `@__NO_SIDE_EFFECTS__`.
    pub fn no_side_effects(&self) -> &FxHashSet<SymbolId> {
        &self.no_side_effects
    }

    /// Get the computed constant value for an enum member symbol.
    pub fn get_enum_member_value(&self, symbol_id: SymbolId) -> Option<&ConstantValue> {
        self.enum_data.get_member_value(symbol_id)
    }

    /// Set a computed constant value for an enum member symbol.
    pub(crate) fn set_enum_member_value(&mut self, symbol_id: SymbolId, value: ConstantValue) {
        self.enum_data.set_member_value(symbol_id, value);
    }

    /// Get the body scopes for an enum declaration symbol.
    /// Returns multiple scopes for merged enum declarations.
    pub fn get_enum_body_scopes(&self, symbol_id: SymbolId) -> Option<&[ScopeId]> {
        self.enum_data.get_body_scopes(symbol_id)
    }

    /// Add a body scope for an enum declaration symbol.
    /// Appends to the list (supports merged enum declarations).
    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);
    }
}

/// Scope Tree Methods
impl Scoping {
    const ROOT_SCOPE_ID: ScopeId = ScopeId::new(0);

    /// Returns the number of scopes found in the program. Includes the root
    /// program scope.
    #[inline]
    pub fn scopes_len(&self) -> usize {
        self.scope_table.len()
    }

    /// Returns `true` if there are no scopes in the program.
    ///
    /// This will always return `false` when semantic analysis has completed
    /// since there is a root scope.
    #[inline]
    pub fn scopes_is_empty(&self) -> bool {
        self.scope_table.is_empty()
    }

    /// Iterate over the scopes that contain a scope.
    ///
    /// The first element of this iterator will be the scope itself. This
    /// guarantees the iterator will have at least 1 element.
    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))
    }

    /// Iterate all scope IDs from the root scope through all descendants.
    pub fn scope_descendants_from_root(&self) -> impl Iterator<Item = ScopeId> + '_ {
        self.scope_table.iter_ids()
    }

    /// Get the root [`Program`] scope id.
    ///
    /// [`Program`]: oxc_ast::ast::Program
    #[expect(clippy::unused_self)]
    #[inline]
    pub const fn root_scope_id(&self) -> ScopeId {
        Self::ROOT_SCOPE_ID
    }

    /// Get the flags for the root scope.
    ///
    /// This is a shorthand for `scope.get_flags(scope.root_scope_id())`.
    #[inline]
    pub fn root_scope_flags(&self) -> ScopeFlags {
        *self.scope_table.flags(self.root_scope_id())
    }

    #[inline]
    /// Unresolved references recorded in the root scope.
    pub fn root_unresolved_references(&self) -> &UnresolvedReferences<'_> {
        &self.cell.borrow_dependent().root_unresolved_references
    }

    /// Iterate unresolved reference IDs grouped by unresolved identifier name.
    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())
    }

    /// Delete an unresolved reference.
    ///
    /// If the `ReferenceId` provided is only reference remaining for this unresolved reference
    /// (i.e. this `x` was last `x` in the AST), deletes the key from `root_unresolved_references`.
    ///
    /// # Panics
    /// Panics if there is no unresolved reference for provided `name` and `reference_id`.
    #[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]
    /// Get scope flags for `scope_id`.
    pub fn scope_flags(&self, scope_id: ScopeId) -> ScopeFlags {
        *self.scope_table.flags(scope_id)
    }

    #[inline]
    /// Get mutable scope flags for `scope_id`.
    pub fn scope_flags_mut(&mut self, scope_id: ScopeId) -> &mut ScopeFlags {
        self.scope_table.flags_mut(scope_id)
    }

    /// Get [`ScopeFlags`] for a new child scope under `parent_scope_id`.
    pub fn get_new_scope_flags(&self, flags: ScopeFlags, parent_scope_id: ScopeId) -> ScopeFlags {
        // https://tc39.es/ecma262/#sec-strict-mode-code
        flags | self.scope_flags(parent_scope_id) & ScopeFlags::StrictMode
    }

    #[inline]
    /// Get the parent scope ID, if any.
    pub fn scope_parent_id(&self, scope_id: ScopeId) -> Option<ScopeId> {
        *self.scope_table.parent_ids(scope_id)
    }

    /// Set the parent 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;
    }

    /// Change the parent scope of a scope.
    ///
    /// This will also remove the scope from the child list of the old parent and add it to the new parent.
    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;
    }

    /// Get a variable binding by name that was declared in the top-level scope
    #[inline]
    pub fn get_root_binding(&self, name: Ident<'_>) -> Option<SymbolId> {
        self.get_binding(self.root_scope_id(), name)
    }

    /// Add an unresolved reference to the root scope map.
    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);
        });
    }

    /// Check if a symbol is declared in a certain scope.
    pub fn scope_has_binding(&self, scope_id: ScopeId, name: Ident<'_>) -> bool {
        self.cell.borrow_dependent().bindings[scope_id].contains_key(&name)
    }

    /// Get the symbol bound to an identifier name in a scope.
    ///
    /// Returns [`None`] if that name is not bound in the scope. This could be
    /// because the symbol is not declared within this tree, but it could also
    /// be because its declaration is in a parent scope. If you want to find a
    /// binding that might be declared in a parent scope, use [`find_binding`].
    ///
    /// [`find_binding`]: Scoping::find_binding
    pub fn get_binding(&self, scope_id: ScopeId, name: Ident<'_>) -> Option<SymbolId> {
        self.cell.borrow_dependent().bindings[scope_id].get(&name).copied()
    }

    /// Find a binding by name in a scope or its ancestors.
    ///
    /// Bindings are resolved by walking up the scope tree until a binding is
    /// found. If no binding is found, [`None`] is returned.
    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;
        }
    }

    /// Get all bound identifiers in a scope.
    #[inline]
    pub fn get_bindings(&self, scope_id: ScopeId) -> &Bindings<'_> {
        &self.cell.borrow_dependent().bindings[scope_id]
    }

    /// Get the ID of the [`AstNode`] that created a scope.
    ///
    /// [`AstNode`]: crate::AstNode
    #[inline]
    pub fn get_node_id(&self, scope_id: ScopeId) -> NodeId {
        *self.scope_table.node_ids(scope_id)
    }

    /// Iterate over all bindings declared in the entire program.
    ///
    /// If you only want bindings in a specific scope, use [`iter_bindings_in`].
    ///
    /// [`iter_bindings_in`]: Scoping::iter_bindings_in
    pub fn iter_bindings(&self) -> impl Iterator<Item = (ScopeId, &Bindings<'_>)> + '_ {
        self.cell.borrow_dependent().bindings.iter_enumerated()
    }

    /// Iterate over bindings declared inside a scope.
    #[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);
        });
    }

    /// Create a scope.
    #[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
    }

    /// Add a binding to a scope.
    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);
        });
    }

    /// Remove an existing binding from a scope.
    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());
        });
    }

    /// Move a binding from one scope to another.
    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);
            }
        });
    }

    /// Rename a binding to a new name.
    ///
    /// The following must be true for successful operation:
    /// * Binding exists in specified scope for `old_name`.
    /// * Existing binding is for specified `symbol_id`.
    /// * No binding already exists for `new_name`.
    ///
    /// Panics in debug mode if any of the above are not true.
    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());
        });
    }

    /// Rename symbol.
    ///
    /// The following must be true for successful operation:
    /// * Binding exists in specified scope for `symbol_id`.
    /// * No binding already exists in scope for `new_name`.
    ///
    /// Panics in debug mode if either of the above are not satisfied.
    pub fn rename_symbol(&mut self, symbol_id: SymbolId, scope_id: ScopeId, new_name: Ident<'_>) {
        self.cell.with_dependent_mut(|allocator, cell| {
            // Rename symbol
            let new_name = new_name.clone_in(allocator);
            let old_name = mem::replace(&mut cell.symbol_names[symbol_id.index()], new_name);

            // Rename binding, same as `Self::rename_binding`, we cannot call it directly
            // because the `old_name` borrowed `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 existing_symbol_id = bindings.insert(new_name, symbol_id);
            debug_assert!(existing_symbol_id.is_none());
        });
    }

    /// Remove bindings that exist only in TypeScript type space.
    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 {
    /// Clone all semantic data. Used in `Rolldown`.
    #[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),
                })
            },
        }
    }
}