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}