Skip to main content

oxc_syntax/
reference.rs

1use bitflags::bitflags;
2use oxc_index::define_nonmax_u32_index_type;
3#[cfg(feature = "serialize")]
4use serde::Serialize;
5
6use oxc_allocator::{Allocator, CloneIn};
7
8use crate::{node::NodeId, scope::ScopeId, symbol::SymbolId};
9
10use oxc_ast_macros::ast;
11
12define_nonmax_u32_index_type! {
13    #[ast]
14    #[builder(default)]
15    #[clone_in(default)]
16    #[content_eq(skip)]
17    #[estree(skip)]
18    pub struct ReferenceId;
19}
20
21impl<'alloc> CloneIn<'alloc> for ReferenceId {
22    type Cloned = Self;
23
24    fn clone_in(&self, _: &'alloc Allocator) -> Self {
25        // `clone_in` should never reach this, because `CloneIn` skips reference_id field
26        unreachable!();
27    }
28
29    #[expect(clippy::inline_always)]
30    #[inline(always)]
31    fn clone_in_with_semantic_ids(&self, _: &'alloc Allocator) -> Self {
32        *self
33    }
34}
35
36bitflags! {
37    /// Describes how a symbol is being referenced in the AST.
38    ///
39    /// There are three general categories of references:
40    /// 1. Values being referenced as values
41    /// 2. Types being referenced as types
42    /// 3. Values being used in type contexts
43    ///
44    /// ## Values
45    /// Whether a reference is considered [`Read`] or [`Write`] is determined according to ECMA spec.
46    ///
47    /// See comments on [`Read`] and [`Write`] below.
48    ///
49    /// Counter-intuitively, `y` in `x = y = z` is [`Write`] only. `x = y = z` is equivalent to:
50    ///
51    /// ```js
52    /// var _temp = z;
53    /// y = _temp;
54    /// x = _temp;
55    /// ```
56    ///
57    /// See <https://github.com/oxc-project/oxc/issues/5165#issuecomment-2488333549> for a runtime test
58    /// to determine Read/Write operations in a code snippet.
59    ///
60    /// ## Value as Type
61    /// The [`ValueAsType`] flag is a temporary marker for references that need to
62    /// resolve to value symbols initially, but will ultimately be treated as type references.
63    /// This flag is crucial in scenarios like TypeScript's `typeof` operator.
64    ///
65    /// For example, in `type T = typeof a`:
66    /// 1. The reference to 'a' is initially flagged with [`ValueAsType`].
67    /// 2. This ensures that during symbol resolution, 'a' should be a value symbol.
68    /// 3. However, the final resolved reference's flags will be treated as a type.
69    ///
70    /// ## Types
71    /// Type references are indicated by [`Type`]. These are used primarily in
72    /// type definitions and signatures. Types can never be re-assigned, so
73    /// there is no read/write distinction for type references.
74    ///
75    /// [`Read`]: ReferenceFlags::Read
76    /// [`Write`]: ReferenceFlags::Write
77    /// [`Type`]: ReferenceFlags::Type
78    /// [`ValueAsType`]: ReferenceFlags::ValueAsType
79    #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
80    #[cfg_attr(feature = "serialize", derive(Serialize))]
81    pub struct ReferenceFlags: u8 {
82        const None = 0;
83        /// Symbol is being read from as a Value.
84        ///
85        /// Whether a reference is `Read` is as defined in the spec:
86        ///
87        /// Under `Runtime Semantics: Evaluation`, when [`GetValue`](https://tc39.es/ecma262/#sec-getvalue)
88        /// is called on a expression, and the expression is an `IdentifierReference`.
89        ///
90        /// For example:
91        /// ```text
92        /// 1. Let lRef be ? Evaluation of Expression.
93        /// 2. Perform ? GetValue(lRef).
94        /// ```
95        const Read = 1 << 0;
96        /// Symbol is being written to as a Value.
97        ///
98        /// Whether a reference is `Write` is as defined in the spec:
99        ///
100        /// Under `Runtime Semantics: Evaluation`, when [`PutValue`](https://tc39.es/ecma262/#sec-putvalue)
101        /// is called on a expression, and the expression is an `IdentifierReference`.
102        ///
103        /// For example:
104        /// ```text
105        /// 1. Let lhs be ? Evaluation of LeftHandSideExpression.
106        /// 2. Perform ? PutValue(lhs, newValue).
107        /// ```
108        const Write = 1 << 1;
109        /// Used in type definitions.
110        const Type = 1 << 2;
111        /// A value symbol is used in a type context, such as in `typeof` expressions.
112        const ValueAsType = 1 << 3;
113        /// Reference must resolve to a namespace (module, namespace, enum).
114        ///
115        /// Used for the left side of qualified names like `Database.Table`.
116        /// This ensures the reference resolves to a namespace/module rather than
117        /// a type parameter that might shadow it, since type parameters cannot
118        /// have member access.
119        const Namespace = 1 << 4;
120        /// The identifier is read as the object of a member expression in a
121        /// property modification context. For example, `A` in `A.foo = 1`,
122        /// `A.foo += 1`, `++A.foo`, `delete A.foo`, or `for (A.foo in obj)`.
123        ///
124        /// This flag is always combined with [`Read`] (since the identifier is
125        /// read to access the property). It helps the minifier and linter
126        /// determine if a symbol's only reads are property-modification targets,
127        /// enabling dead code elimination and unused variable detection.
128        ///
129        /// [`Read`]: ReferenceFlags::Read
130        const MemberWriteTarget = 1 << 5;
131        /// The symbol being referenced is a value.
132        ///
133        /// Note that this does not necessarily indicate the reference is used
134        /// in a value context, since type queries are also flagged as [`Read`].
135        ///
136        /// [`Read`]: ReferenceFlags::Read
137        const Value = Self::Read.bits() | Self::Write.bits();
138    }
139}
140
141impl ReferenceFlags {
142    #[inline]
143    pub const fn read() -> Self {
144        Self::Read
145    }
146
147    #[inline]
148    pub const fn write() -> Self {
149        Self::Write
150    }
151
152    #[inline]
153    pub const fn read_write() -> Self {
154        Self::Value
155    }
156
157    /// The identifier is read from. It may also be written to.
158    #[inline]
159    pub const fn is_read(self) -> bool {
160        self.intersects(Self::Read)
161    }
162
163    /// The identifier is only read from.
164    #[inline]
165    pub const fn is_read_only(self) -> bool {
166        !self.contains(Self::Write)
167    }
168
169    /// The identifier is written to. It may also be read from.
170    #[inline]
171    pub const fn is_write(self) -> bool {
172        self.intersects(Self::Write)
173    }
174
175    /// The identifier is only written to. It is not read from in this reference.
176    #[inline]
177    pub const fn is_write_only(self) -> bool {
178        self.intersects(Self::Write) && !self.contains(Self::Read)
179    }
180
181    /// The identifier is both read from and written to, e.g `a += 1`.
182    #[inline]
183    pub fn is_read_write(self) -> bool {
184        self.contains(Self::Read | Self::Write)
185    }
186
187    /// The identifier is read as the object of a member expression
188    /// in a property modification context (e.g., `A` in `A.foo = 1`,
189    /// `A.foo += 1`, `++A.foo`, `delete A.foo`, or `for (A.foo in obj)`).
190    #[inline]
191    pub const fn is_member_write_target(self) -> bool {
192        self.intersects(Self::MemberWriteTarget)
193    }
194
195    /// Checks if the reference is a value being used in a type context.
196    #[inline]
197    pub fn is_value_as_type(self) -> bool {
198        self.contains(Self::ValueAsType)
199    }
200
201    /// The identifier is used in a type definition.
202    #[inline]
203    pub const fn is_type(self) -> bool {
204        self.contains(Self::Type)
205    }
206
207    #[inline]
208    pub const fn is_type_only(self) -> bool {
209        matches!(self, Self::Type)
210    }
211
212    #[inline]
213    pub const fn is_value(self) -> bool {
214        self.intersects(Self::Value)
215    }
216
217    /// Checks if the reference must resolve to a namespace.
218    #[inline]
219    pub const fn is_namespace(self) -> bool {
220        self.contains(Self::Namespace)
221    }
222}
223
224impl<'alloc> CloneIn<'alloc> for ReferenceFlags {
225    type Cloned = Self;
226
227    fn clone_in(&self, _: &'alloc oxc_allocator::Allocator) -> Self::Cloned {
228        *self
229    }
230}
231
232/// Describes where and how a Symbol is used in the AST.
233///
234/// References indicate how they are being used using [`ReferenceFlags`]. Refer
235/// to the documentation for [`ReferenceFlags`] for more information.
236///
237/// ## Resolution
238/// References to symbols that could be resolved have their `symbol_id` field
239/// populated. [`None`] indicates that either a global variable or a
240/// non-existent symbol is being referenced.
241///
242/// The node identified by `node_id` will be an `IdentifierReference`.
243/// Note that declarations do not count as references, even if the declaration
244/// is being used in an expression.
245///
246/// ```ts
247/// const arr = [1, 2, 3].map(function mapper(x) { return x + 1; });
248/// //      Not considered a reference ^^^^^^
249/// ```
250#[cfg_attr(feature = "serialize", derive(Serialize), serde(rename_all = "camelCase"))]
251#[derive(Debug, Clone)]
252pub struct Reference {
253    /// The AST node making the reference.
254    node_id: NodeId,
255    /// The symbol being referenced.
256    ///
257    /// This will be [`None`] if no symbol could be found within
258    /// the reference's scope tree. Usually this indicates a global variable or
259    /// a reference to a non-existent symbol.
260    symbol_id: Option<SymbolId>,
261    /// The scope in which this reference occurs.
262    scope_id: ScopeId,
263    /// Describes how this referenced is used by other AST nodes. References can
264    /// be reads, writes, or both.
265    flags: ReferenceFlags,
266}
267
268impl Reference {
269    /// Create a new unresolved reference.
270    #[inline]
271    pub fn new(node_id: NodeId, scope_id: ScopeId, flags: ReferenceFlags) -> Self {
272        Self { node_id, symbol_id: None, scope_id, flags }
273    }
274
275    /// Create a new resolved reference on a symbol.
276    #[inline]
277    pub fn new_with_symbol_id(
278        node_id: NodeId,
279        symbol_id: SymbolId,
280        scope_id: ScopeId,
281        flags: ReferenceFlags,
282    ) -> Self {
283        Self { node_id, symbol_id: Some(symbol_id), scope_id, flags }
284    }
285
286    /// Get the id of the node that is referencing the symbol.
287    #[inline]
288    pub fn node_id(&self) -> NodeId {
289        self.node_id
290    }
291
292    /// Get the id of the symbol being referenced.
293    ///
294    /// Will return [`None`] if the symbol could not be resolved.
295    #[inline]
296    pub fn symbol_id(&self) -> Option<SymbolId> {
297        self.symbol_id
298    }
299
300    #[inline]
301    pub fn set_symbol_id(&mut self, symbol_id: SymbolId) {
302        self.symbol_id = Some(symbol_id);
303    }
304
305    /// Get the id of the scope in which this reference occurs.
306    #[inline]
307    pub fn scope_id(&self) -> ScopeId {
308        self.scope_id
309    }
310
311    #[inline]
312    pub fn flags(&self) -> ReferenceFlags {
313        self.flags
314    }
315
316    #[inline]
317    pub fn flags_mut(&mut self) -> &mut ReferenceFlags {
318        &mut self.flags
319    }
320
321    /// Returns `true` if the identifier value was read.
322    ///
323    /// This is not mutually exclusive with [`Reference::is_write`].
324    #[inline]
325    pub fn is_read(&self) -> bool {
326        self.flags.is_read()
327    }
328
329    /// Returns `true` if the identifier was written to.
330    ///
331    /// This is not mutually exclusive with [`Reference::is_read`].
332    #[inline]
333    pub fn is_write(&self) -> bool {
334        self.flags.is_write()
335    }
336
337    /// Returns `true` if this reference is used in a value context.
338    pub fn is_value(&self) -> bool {
339        self.flags.is_value()
340    }
341
342    /// Returns `true` if this reference is used in a type context.
343    #[inline]
344    pub fn is_type(&self) -> bool {
345        self.flags.is_type()
346    }
347}