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}