oxc_syntax/
symbol.rs

1#![expect(missing_docs)] // fixme
2use bitflags::bitflags;
3use nonmax::NonMaxU32;
4use oxc_allocator::{Allocator, CloneIn};
5use oxc_index::Idx;
6#[cfg(feature = "serialize")]
7use serde::{Serialize, Serializer};
8
9use oxc_ast_macros::ast;
10
11#[ast]
12#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
13#[builder(default)]
14#[clone_in(default)]
15#[content_eq(skip)]
16#[estree(skip)]
17pub struct SymbolId(NonMaxU32);
18
19impl SymbolId {
20    /// Create `SymbolId` from `u32`.
21    ///
22    /// # Panics
23    /// Panics if `idx` is `u32::MAX`.
24    pub const fn new(idx: u32) -> Self {
25        if let Some(idx) = NonMaxU32::new(idx) {
26            return Self(idx);
27        }
28        panic!();
29    }
30
31    /// Create `SymbolId` from `u32` unchecked.
32    ///
33    /// # SAFETY
34    /// `idx` must not be `u32::MAX`.
35    pub const unsafe fn new_unchecked(idx: u32) -> Self {
36        // SAFETY: Caller must ensure `idx` is not `u32::MAX`
37        unsafe { Self(NonMaxU32::new_unchecked(idx)) }
38    }
39}
40
41impl Idx for SymbolId {
42    #[expect(clippy::cast_possible_truncation)]
43    fn from_usize(idx: usize) -> Self {
44        assert!(idx < u32::MAX as usize);
45        // SAFETY: We just checked `idx` is a legal value for `NonMaxU32`
46        Self(unsafe { NonMaxU32::new_unchecked(idx as u32) })
47    }
48
49    fn index(self) -> usize {
50        self.0.get() as usize
51    }
52}
53
54impl<'alloc> CloneIn<'alloc> for SymbolId {
55    type Cloned = Self;
56
57    fn clone_in(&self, _: &'alloc Allocator) -> Self {
58        // `clone_in` should never reach this, because `CloneIn` skips symbol_id field
59        unreachable!();
60    }
61
62    #[expect(clippy::inline_always)]
63    #[inline(always)]
64    fn clone_in_with_semantic_ids(&self, _: &'alloc Allocator) -> Self {
65        *self
66    }
67}
68
69#[cfg(feature = "serialize")]
70impl Serialize for SymbolId {
71    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
72        serializer.serialize_u32(self.0.get())
73    }
74}
75
76#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
77pub struct RedeclarationId(NonMaxU32);
78
79impl Idx for RedeclarationId {
80    #[expect(clippy::cast_possible_truncation)]
81    fn from_usize(idx: usize) -> Self {
82        assert!(idx < u32::MAX as usize);
83        // SAFETY: We just checked `idx` is valid for `NonMaxU32`
84        Self(unsafe { NonMaxU32::new_unchecked(idx as u32) })
85    }
86
87    fn index(self) -> usize {
88        self.0.get() as usize
89    }
90}
91
92#[cfg(feature = "serialize")]
93impl Serialize for RedeclarationId {
94    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
95        serializer.serialize_u32(self.0.get())
96    }
97}
98
99bitflags! {
100    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
101    #[cfg_attr(feature = "serialize", derive(Serialize))]
102    pub struct SymbolFlags: u32 {
103        const None                    = 0;
104        /// Variable (var) or parameter
105        const FunctionScopedVariable  = 1 << 0;
106        /// A block-scoped variable (let or const)
107        const BlockScopedVariable     = 1 << 1;
108        /// A const variable (const)
109        const ConstVariable           = 1 << 2;
110        const Class                   = 1 << 3;
111        /// `try {} catch(catch_variable) {}`
112        const CatchVariable           = 1 << 4;
113        /// A function declaration or expression
114        const Function                = 1 << 5;
115        /// Imported ESM binding
116        const Import                  = 1 << 6;
117        /// Imported ESM type-only binding
118        const TypeImport              = 1 << 7;
119        // Type specific symbol flags
120        const TypeAlias               = 1 << 8;
121        const Interface               = 1 << 9;
122        const RegularEnum             = 1 << 10;
123        const ConstEnum               = 1 << 11;
124        const EnumMember              = 1 << 12;
125        const TypeParameter           = 1 << 13;
126        /// Uninstantiated module
127        const NamespaceModule         = 1 << 14;
128        /// Instantiated module
129        const ValueModule             = 1 << 15;
130        /// Declared with `declare` modifier, like `declare function x() {}`.
131        //
132        // This flag is not part of TypeScript's `SymbolFlags`, it comes from TypeScript's `NodeFlags`. We introduced it into
133        // here because `NodeFlags` is incomplete and we only can access to `NodeFlags` in the Semantic, but we also need to
134        // access it in the Transformer.
135        // https://github.com/microsoft/TypeScript/blob/15392346d05045742e653eab5c87538ff2a3c863/src/compiler/types.ts#L819-L820
136        const Ambient                 = 1 << 16;
137
138        const Enum = Self::ConstEnum.bits() | Self::RegularEnum.bits();
139        const Variable = Self::FunctionScopedVariable.bits() | Self::BlockScopedVariable.bits();
140
141        const BlockScoped = Self::BlockScopedVariable.bits() | Self::Enum.bits() | Self::Class.bits();
142
143        const Value = Self::Variable.bits() | Self::Class.bits() | Self::Function.bits() | Self::Enum.bits() | Self::EnumMember.bits() | Self::ValueModule.bits();
144        const Type = Self::Class.bits() | Self::Interface.bits() | Self::Enum.bits() | Self::EnumMember.bits() | Self::TypeParameter.bits()  |  Self::TypeAlias.bits();
145        const Namespace = Self::ValueModule.bits() | Self::NamespaceModule.bits() | Self::Enum.bits();
146
147
148        /// Variables can be redeclared, but can not redeclare a block-scoped declaration with the
149        /// same name, or any other value that is not a variable, e.g. ValueModule or Class
150        const FunctionScopedVariableExcludes = Self::Value.bits() - Self::FunctionScopedVariable.bits() - Self::Function.bits();
151
152        /// Block-scoped declarations are not allowed to be re-declared
153        /// they can not merge with anything in the value space
154        const BlockScopedVariableExcludes = Self::Value.bits();
155        const FunctionExcludes = Self::Value.bits() & !(Self::Function.bits() | Self::ValueModule.bits() | Self::Class.bits());
156        const ClassExcludes = (Self::Value.bits() | Self::Type.bits()) & !(Self::ValueModule.bits() | Self::Function.bits() | Self::Interface.bits());
157
158        const ImportBindingExcludes = Self::Import.bits() | Self::TypeImport.bits();
159        // Type specific excludes
160        const TypeAliasExcludes = Self::Type.bits();
161        const InterfaceExcludes = Self::Type.bits() & !(Self::Interface.bits() | Self::Class.bits());
162        const TypeParameterExcludes = Self::Type.bits() & !Self::TypeParameter.bits();
163        const ConstEnumExcludes = (Self::Type.bits() | Self::Value.bits()) & !Self::ConstEnum.bits();
164        const ValueModuleExcludes = Self::Value.bits() & !(Self::Function.bits() | Self::Class.bits() | Self::RegularEnum.bits() | Self::ValueModule.bits());
165        const NamespaceModuleExcludes = 0;
166        // TODO: include value module in regular enum excludes
167        const RegularEnumExcludes = (Self::Value.bits() | Self::Type.bits()) & !(Self::RegularEnum.bits() | Self::ValueModule.bits() );
168        const EnumMemberExcludes = Self::EnumMember.bits();
169
170    }
171}
172
173impl SymbolFlags {
174    #[inline]
175    pub fn is_variable(self) -> bool {
176        self.intersects(Self::Variable)
177    }
178
179    #[inline]
180    pub fn is_type_parameter(self) -> bool {
181        self.contains(Self::TypeParameter)
182    }
183
184    /// If true, then the symbol is a type, such as a TypeAlias, Interface, or Enum
185    #[inline]
186    pub fn is_type(self) -> bool {
187        self.intersects((Self::TypeImport | Self::Type) - Self::Value)
188    }
189
190    /// If true, then the symbol is a value, such as a Variable, Function, or Class
191    #[inline]
192    pub fn is_value(self) -> bool {
193        self.intersects(Self::Value | Self::Import)
194    }
195
196    #[inline]
197    pub fn is_const_variable(self) -> bool {
198        self.contains(Self::ConstVariable)
199    }
200
201    /// Returns `true` if this symbol is a function declaration or expression.
202    #[inline]
203    pub fn is_function(self) -> bool {
204        self.contains(Self::Function)
205    }
206
207    #[inline]
208    pub fn is_class(self) -> bool {
209        self.contains(Self::Class)
210    }
211
212    #[inline]
213    pub fn is_interface(self) -> bool {
214        self.contains(Self::Interface)
215    }
216
217    #[inline]
218    pub fn is_type_alias(self) -> bool {
219        self.contains(Self::TypeAlias)
220    }
221
222    #[inline]
223    pub fn is_enum(self) -> bool {
224        self.intersects(Self::Enum)
225    }
226
227    #[inline]
228    pub fn is_const_enum(self) -> bool {
229        self.intersects(Self::ConstEnum)
230    }
231
232    #[inline]
233    pub fn is_enum_member(self) -> bool {
234        self.contains(Self::EnumMember)
235    }
236
237    #[inline]
238    pub fn is_catch_variable(self) -> bool {
239        self.contains(Self::CatchVariable)
240    }
241
242    #[inline]
243    pub fn is_function_scoped_declaration(self) -> bool {
244        self.contains(Self::FunctionScopedVariable)
245    }
246
247    #[inline]
248    pub fn is_import(self) -> bool {
249        self.intersects(Self::Import | Self::TypeImport)
250    }
251
252    #[inline]
253    pub fn is_type_import(self) -> bool {
254        self.contains(Self::TypeImport)
255    }
256
257    #[inline]
258    pub fn is_ambient(self) -> bool {
259        self.contains(Self::Ambient)
260    }
261
262    #[inline]
263    pub fn is_namespace_module(self) -> bool {
264        self.contains(Self::NamespaceModule)
265    }
266
267    #[inline]
268    pub fn is_value_module(self) -> bool {
269        self.contains(Self::ValueModule)
270    }
271
272    /// If true, then the symbol can be referenced by a type reference
273    #[inline]
274    pub fn can_be_referenced_by_type(self) -> bool {
275        self.intersects(Self::Type | Self::TypeImport | Self::Import | Self::Namespace)
276    }
277
278    /// If true, then the symbol can be referenced by a value reference
279    #[inline]
280    pub fn can_be_referenced_by_value(self) -> bool {
281        self.is_value()
282    }
283
284    /// If true, then the symbol can be referenced by a value_as_type reference
285    #[inline]
286    pub fn can_be_referenced_by_value_as_type(self) -> bool {
287        self.intersects(Self::Value | Self::Import | Self::Function | Self::TypeImport)
288    }
289}