oxc_syntax/
reference.rs

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