Skip to main content

nodejs/
ast.rs

1//! JavaScript abstract syntax tree.
2//!
3//! Every node here has a direct lowering in `compiler.rs`. JS is
4//! statement-oriented with brace-delimited blocks, so the tree separates `Stmt`
5//! (blocks of these form a program/function body) from `Expr`. Numbers are all
6//! IEEE-754 `f64`, matching JavaScript's single number type.
7
8/// A source range as UTF-8 byte offsets `(start, end)` into the text the
9/// parser read. `(0, 0)` means none was recorded (a `${…}` template field is
10/// re-parsed from a copy, so its offsets would not point into the script).
11/// `Function.prototype.toString` returns this slice (20.2.3.5).
12pub type Span = (u32, u32);
13
14/// A binary operator (`a <op> b`). `&&`/`||`/`??` are `LogicalOp` because they
15/// short-circuit and yield an operand value, not a coerced boolean.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum BinOp {
18    Add,
19    Sub,
20    Mul,
21    Div,
22    Mod,
23    Pow, // **
24    // Comparison
25    Lt,
26    Le,
27    Gt,
28    Ge,
29    EqEqEq, // ===
30    NeEqEq, // !==
31    EqEq,   // ==  (loose, coercing)
32    NeEq,   // !=
33    // Bitwise / shift
34    BitAnd,
35    BitOr,
36    BitXor,
37    Shl,  // <<
38    Shr,  // >>
39    UShr, // >>>
40    // `in` / `instanceof`
41    In,
42    InstanceOf,
43}
44
45/// A short-circuiting logical operator.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum LogicalOp {
48    And,     // &&
49    Or,      // ||
50    Nullish, // ??
51}
52
53/// The operator carried by a COMPOUND assignment (`a += b`, `a ??= b`).
54///
55/// It rides on the [`Expr::Assign`] node rather than being desugared away at
56/// parse time. The parser used to rewrite `a op= b` into `a = a op b`, which
57/// duplicates the target expression — so every side effect in the target ran
58/// TWICE. `o[k()] += 1` called `k` once in node and twice here, and the same
59/// held for the logical forms even when they short-circuited and never wrote.
60/// Duplication cannot be undone later by inspecting the tree, because
61/// `o[k()] = o[k()] + 1` is a DIFFERENT program that legitimately calls `k`
62/// twice and is structurally identical after the rewrite.
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum AssignOp {
65    /// `+= -= *= /= %= **= &= |= ^= <<= >>= >>>=` — always reads, computes, writes.
66    Binary(BinOp),
67    /// `&&= ||= ??=` — reads, and writes only if the read did not short-circuit.
68    Logical(LogicalOp),
69}
70
71/// A unary prefix operator.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum UnOp {
74    Neg,    // -x
75    Pos,    // +x
76    Not,    // !x
77    BitNot, // ~x
78    TypeOf, // typeof x
79    Void,   // void x
80    Delete, // delete x
81}
82
83/// The kind of a variable declaration.
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum DeclKind {
86    Var,
87    Let,
88    Const,
89}
90
91/// The update (increment/decrement) operator, prefix or postfix.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum UpdateOp {
94    Inc, // ++
95    Dec, // --
96}
97
98/// A property of an object literal.
99#[derive(Debug, Clone, PartialEq)]
100pub enum Prop {
101    /// `key: value` — `computed` marks `[expr]: value`.
102    KeyValue {
103        key: Expr,
104        value: Expr,
105        computed: bool,
106    },
107    /// `...spread`.
108    Spread(Expr),
109    /// `get key() {}` / `set key(v) {}` — an accessor property.
110    Accessor {
111        key: Expr,
112        computed: bool,
113        /// `true` for a getter, `false` for a setter.
114        is_getter: bool,
115        /// The accessor function (an `Expr::Function`).
116        func: Expr,
117    },
118}
119
120/// A JavaScript expression.
121#[derive(Debug, Clone, PartialEq)]
122pub enum Expr {
123    Null,
124    Undefined,
125    True,
126    False,
127    Number(f64),
128    /// A `BigInt` literal as its canonical decimal digit string (`"123"` for
129    /// `123n`). Lowered to a heap `JsObj::BigInt`.
130    BigInt(String),
131    /// A regex literal: `(pattern, flags)`. Lowered to a `JsObj::RegExp`.
132    Regex(String, String),
133    Str(String),
134    /// A template literal: alternating literal quasis and interpolated exprs.
135    /// `quasis.len() == exprs.len() + 1`.
136    Template {
137        quasis: Vec<String>,
138        exprs: Vec<Expr>,
139    },
140    /// A tagged template `` tag`a${x}b` ``: calls `tag(strings, ...values)` where
141    /// `strings` is the cooked-quasi array carrying a `.raw` array of `raws`.
142    TaggedTemplate {
143        tag: Box<Expr>,
144        quasis: Vec<String>,
145        raws: Vec<String>,
146        exprs: Vec<Expr>,
147    },
148
149    /// A bare identifier (`x`); the compiler resolves scope at runtime.
150    Ident(String),
151    /// `this`.
152    This,
153    /// `super` (only valid as `super(...)` call callee or `super.x` object).
154    Super,
155    /// `new.target`.
156    NewTarget,
157    /// `yield expr` / `yield* expr` / `yield` (generator).
158    Yield {
159        arg: Option<Box<Expr>>,
160        delegate: bool,
161    },
162    /// `await expr` (async function).
163    Await(Box<Expr>),
164    /// A `class` expression.
165    Class(Box<ClassNode>),
166
167    Array(Vec<Expr>),
168    /// An elided array-literal element (`[1,,3]`) — a HOLE, which reads back as
169    /// `undefined` but is not an own property. Distinct from `Expr::Undefined`
170    /// so `compile_array` can record it and `destructure_array` can skip it.
171    Hole,
172    Object(Vec<Prop>),
173    /// `...expr` — a spread element (array/call).
174    Spread(Box<Expr>),
175
176    Logical(LogicalOp, Box<Expr>, Box<Expr>),
177    Unary(UnOp, Box<Expr>),
178    Binary(BinOp, Box<Expr>, Box<Expr>),
179
180    /// `test ? cons : alt`.
181    Conditional {
182        test: Box<Expr>,
183        cons: Box<Expr>,
184        alt: Box<Expr>,
185    },
186
187    /// `target = value`, or a compound `target op= value` when `op` is `Some`.
188    ///
189    /// A compound assignment evaluates the target reference ONCE: for
190    /// `o[k()] += 1` the object and the key are computed a single time, the
191    /// old value is read through them, and the result is written back through
192    /// the same reference (ECMA-262 13.15.2).
193    Assign {
194        target: Box<Expr>,
195        op: Option<AssignOp>,
196        value: Box<Expr>,
197    },
198    /// `++x` / `x++` / `--x` / `x--`.
199    Update {
200        op: UpdateOp,
201        prefix: bool,
202        target: Box<Expr>,
203    },
204
205    /// A call `func(args)`. `optional` marks `?.(`.
206    Call {
207        func: Box<Expr>,
208        args: Vec<Expr>,
209        optional: bool,
210    },
211    /// `new Ctor(args)`.
212    New {
213        callee: Box<Expr>,
214        args: Vec<Expr>,
215    },
216    /// `value.name` — `optional` marks `?.name`.
217    Member {
218        object: Box<Expr>,
219        property: String,
220        optional: bool,
221    },
222    /// `value[expr]` — `optional` marks `?.[expr]`.
223    Index {
224        object: Box<Expr>,
225        index: Box<Expr>,
226        optional: bool,
227    },
228
229    /// A function expression / arrow function.
230    Function {
231        params: Vec<Param>,
232        body: FnBody,
233        is_arrow: bool,
234        name: Option<String>,
235        is_generator: bool,
236        is_async: bool,
237        /// True for a MethodDefinition (`{ m(){} }`, `{ get x(){} }`) rather
238        /// than an ordinary function expression. A non-generator method owns no
239        /// `prototype` property (10.2.5 runs only for ordinary functions).
240        is_method: bool,
241        /// The definition's source text: from `async`/`function`/`get`/`*`/the
242        /// key or the arrow's parameters, to its last token.
243        span: Span,
244    },
245
246    /// `,`-sequence expression: evaluate all, yield the last.
247    Sequence(Vec<Expr>),
248}
249
250/// A `class` declaration/expression body.
251#[derive(Debug, Clone, PartialEq)]
252pub struct ClassNode {
253    pub name: Option<String>,
254    /// The `extends` expression, if any.
255    pub parent: Option<Box<Expr>>,
256    pub members: Vec<ClassMember>,
257    /// `class` through the closing brace.
258    pub span: Span,
259}
260
261/// One member of a class body: a method, accessor, or field, on the instance or
262/// static side.
263#[derive(Debug, Clone, PartialEq)]
264pub struct ClassMember {
265    /// The property key (an `Expr::Str` for a plain name, or any expr when
266    /// `computed`).
267    pub key: Expr,
268    pub computed: bool,
269    pub kind: MemberKind,
270    pub is_static: bool,
271    pub is_generator: bool,
272    pub is_async: bool,
273    /// Params + body for a method/accessor/constructor.
274    pub params: Vec<Param>,
275    pub body: Vec<Stmt>,
276    /// Initializer expression for a field (`x = expr;`).
277    pub field_init: Option<Expr>,
278    /// A method's source, from its first modifier after `static` (or its
279    /// key) to the closing brace.
280    pub span: Span,
281}
282
283/// The kind of a class member.
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum MemberKind {
286    Constructor,
287    Method,
288    Get,
289    Set,
290    Field,
291    /// A `static { … }` initialization block (ES2022). It has no key and no
292    /// parameters: only `body` is meaningful, and it runs once at class-definition
293    /// time with `this` bound to the constructor.
294    StaticBlock,
295}
296
297/// A function/arrow body: either a brace-delimited statement list or (arrow) a
298/// single expression whose value is returned.
299#[derive(Debug, Clone, PartialEq)]
300pub enum FnBody {
301    Block(Vec<Stmt>),
302    Expr(Box<Expr>),
303}
304
305/// A formal parameter.
306#[derive(Debug, Clone, PartialEq)]
307pub struct Param {
308    /// The binding target — an `Ident`, or an array/object pattern (also an
309    /// `Expr::Array`/`Expr::Object` used as a destructuring target).
310    pub pattern: Expr,
311    /// `= default`.
312    pub default: Option<Expr>,
313    /// `...rest`.
314    pub rest: bool,
315}
316
317/// One `case`/`default` clause of a `switch`.
318#[derive(Debug, Clone, PartialEq)]
319pub struct SwitchCase {
320    /// `None` for the `default:` clause.
321    pub test: Option<Expr>,
322    pub body: Vec<Stmt>,
323}
324
325/// A single declarator inside a `var`/`let`/`const`.
326#[derive(Debug, Clone, PartialEq)]
327pub struct Declarator {
328    pub target: Expr,
329    pub init: Option<Expr>,
330}
331
332/// A JavaScript statement.
333#[derive(Debug, Clone, PartialEq)]
334pub enum StmtKind {
335    /// An expression evaluated for effect (value discarded).
336    Expr(Expr),
337    /// `var`/`let`/`const` declaration list.
338    Decl {
339        kind: DeclKind,
340        decls: Vec<Declarator>,
341    },
342    /// `{ ... }` block.
343    Block(Vec<Stmt>),
344    /// `function name(params) { body }`.
345    FuncDecl {
346        name: String,
347        params: Vec<Param>,
348        body: Vec<Stmt>,
349        is_generator: bool,
350        is_async: bool,
351        span: Span,
352    },
353    /// `class Name … { … }`.
354    ClassDecl(ClassNode),
355
356    If {
357        test: Expr,
358        cons: Box<Stmt>,
359        alt: Option<Box<Stmt>>,
360    },
361    While {
362        test: Expr,
363        body: Box<Stmt>,
364    },
365    DoWhile {
366        body: Box<Stmt>,
367        test: Expr,
368    },
369    /// C-style `for (init; test; update) body`.
370    For {
371        init: Option<Box<Stmt>>,
372        test: Option<Expr>,
373        update: Option<Expr>,
374        body: Box<Stmt>,
375    },
376    /// `for (decl of iterable) body`. `is_await` marks `for await (…)`.
377    ForOf {
378        decl_kind: Option<DeclKind>,
379        target: Expr,
380        iter: Expr,
381        body: Box<Stmt>,
382        is_await: bool,
383    },
384    /// `for (decl in object) body`.
385    ForIn {
386        decl_kind: Option<DeclKind>,
387        target: Expr,
388        object: Expr,
389        body: Box<Stmt>,
390    },
391    Switch {
392        disc: Expr,
393        cases: Vec<SwitchCase>,
394    },
395
396    /// `label: stmt` — a labeled statement (typically a loop), targetable by
397    /// `break label` / `continue label`.
398    Labeled {
399        label: String,
400        body: Box<Stmt>,
401    },
402
403    Return(Option<Expr>),
404    Break(Option<String>),
405    Continue(Option<String>),
406    Throw(Expr),
407    Try {
408        block: Vec<Stmt>,
409        handler: Option<(Option<Expr>, Vec<Stmt>)>, // (param pattern, body)
410        finalizer: Option<Vec<Stmt>>,
411    },
412
413    Empty,
414}
415
416/// A statement plus its 1-based source line.
417#[derive(Debug, Clone, PartialEq)]
418pub struct Stmt {
419    pub kind: StmtKind,
420    pub line: u32,
421}
422
423impl Stmt {
424    pub fn new(kind: StmtKind, line: u32) -> Stmt {
425        Stmt { kind, line }
426    }
427}
428
429impl From<StmtKind> for Stmt {
430    /// Wrap a `StmtKind` as a synthetic statement (line 0).
431    fn from(kind: StmtKind) -> Stmt {
432        Stmt { kind, line: 0 }
433    }
434}